1use iced::{
12 Color, Padding, Theme,
13 widget::{Container, container, text},
14};
15use iced_nodegraph::node_header;
16
17const DEFAULT_CORNER_RADIUS: f32 = 5.0;
20const DEFAULT_BORDER_WIDTH: f32 = 1.0;
22
23#[derive(Debug, Clone)]
28pub struct NodeContentStyle {
29 pub title_background: Color,
31 pub title_text: Color,
33 pub body_background: Color,
35 pub body_text: Color,
37 pub accent: Color,
39 pub corner_radius: f32,
41 pub border_width: f32,
43}
44
45impl NodeContentStyle {
46 pub fn with_geometry(mut self, corner_radius: f32, border_width: f32) -> Self {
48 self.corner_radius = corner_radius;
49 self.border_width = border_width;
50 self
51 }
52
53 fn from_accent(accent: Color, theme: &Theme) -> Self {
58 const DARK_TINT: f32 = 0.35;
59 const LIGHT_TINT: f32 = 0.15;
60
61 let palette = theme.extended_palette();
62 let title_background = if palette.is_dark {
63 Color::from_rgba(
64 accent.r * DARK_TINT,
65 accent.g * DARK_TINT,
66 accent.b * DARK_TINT,
67 0.9,
68 )
69 } else {
70 Color::from_rgba(
71 1.0 - (1.0 - accent.r) * LIGHT_TINT,
72 1.0 - (1.0 - accent.g) * LIGHT_TINT,
73 1.0 - (1.0 - accent.b) * LIGHT_TINT,
74 0.9,
75 )
76 };
77
78 Self {
79 title_background,
80 title_text: palette.background.base.text,
81 body_background: Color::TRANSPARENT,
82 body_text: palette.background.base.text,
83 accent,
84 corner_radius: DEFAULT_CORNER_RADIUS,
85 border_width: DEFAULT_BORDER_WIDTH,
86 }
87 }
88
89 pub fn input(theme: &Theme) -> Self {
91 Self::from_accent(theme.extended_palette().primary.base.color, theme)
92 }
93
94 pub fn process(theme: &Theme) -> Self {
96 Self::from_accent(theme.extended_palette().success.base.color, theme)
97 }
98
99 pub fn output(theme: &Theme) -> Self {
101 Self::from_accent(theme.extended_palette().secondary.base.color, theme)
102 }
103
104 pub fn comment(theme: &Theme) -> Self {
106 let palette = theme.extended_palette();
107 let weak = palette.background.weak.color;
108 let weak_text = palette.background.weak.text;
109
110 Self {
111 title_background: Color::from_rgba(weak.r, weak.g, weak.b, 0.7),
112 title_text: weak_text,
113 body_background: Color::TRANSPARENT,
114 body_text: Color::from_rgba(weak_text.r, weak_text.g, weak_text.b, 0.8),
115 accent: weak,
116 corner_radius: DEFAULT_CORNER_RADIUS,
117 border_width: DEFAULT_BORDER_WIDTH,
118 }
119 }
120
121 pub fn custom(theme: &Theme, accent: Color) -> Self {
123 let palette = theme.extended_palette();
124 let is_dark = palette.is_dark;
125
126 if is_dark {
127 Self {
128 title_background: Color::from_rgba(
129 accent.r * 0.4,
130 accent.g * 0.4,
131 accent.b * 0.4,
132 0.9,
133 ),
134 title_text: palette.background.base.text,
135 body_background: Color::TRANSPARENT,
136 body_text: palette.background.base.text,
137 accent,
138 corner_radius: DEFAULT_CORNER_RADIUS,
139 border_width: DEFAULT_BORDER_WIDTH,
140 }
141 } else {
142 Self {
143 title_background: Color::from_rgba(
144 1.0 - (1.0 - accent.r) * 0.3,
145 1.0 - (1.0 - accent.g) * 0.3,
146 1.0 - (1.0 - accent.b) * 0.3,
147 0.9,
148 ),
149 title_text: palette.background.base.text,
150 body_background: Color::TRANSPARENT,
151 body_text: palette.background.base.text,
152 accent,
153 corner_radius: DEFAULT_CORNER_RADIUS,
154 border_width: DEFAULT_BORDER_WIDTH,
155 }
156 }
157 }
158}
159
160pub fn node_title_bar<'a, Message>(
165 title: impl Into<String>,
166 style: NodeContentStyle,
167) -> Container<'a, Message, Theme, iced::Renderer>
168where
169 Message: Clone + 'a,
170{
171 let title_text = text(title.into()).size(13).color(style.title_text);
172
173 node_header(
174 container(title_text).padding(Padding {
175 top: 4.0,
176 bottom: 4.0,
177 left: 8.0,
178 right: 8.0,
179 }),
180 style.title_background,
181 style.corner_radius,
182 )
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use iced_nodegraph::{NodeStatus, default_node_style};
189
190 #[test]
194 fn default_content_matches_rendered_node() {
195 let theme = Theme::Dark;
196 let fill = default_node_style(&theme, NodeStatus::Idle).corner_radius;
197 let content = NodeContentStyle::input(&theme).corner_radius;
198 assert_eq!(
199 content, fill,
200 "NodeContentStyle default radius must equal the rendered fill radius"
201 );
202 }
203}