1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use std::any::TypeId;
15
16use colors::{ACCENT_COLOR_VAR, BASE_COLOR_VAR};
17use zng_app::event::CommandParam;
18use zng_var::AnyVar;
19use zng_wgt::{base_color, border, corner_radius, is_disabled, node::VarPresent as _, prelude::*};
20use zng_wgt_access::{AccessRole, access_role, labelled_by_child};
21use zng_wgt_container::{Container, child_align, padding};
22use zng_wgt_fill::background_color;
23use zng_wgt_filter::{child_opacity, saturate};
24use zng_wgt_input::{
25 CursorIcon, cursor,
26 focus::FocusableMix,
27 gesture::{ClickArgs, on_click},
28 is_cap_hovered, is_pressed,
29 pointer_capture::{CaptureMode, capture_pointer},
30};
31use zng_wgt_style::{Style, StyleMix, impl_named_style_fn, impl_style_fn};
32use zng_wgt_text::{FONT_COLOR_VAR, Text, font_color, txt_selectable_alt_only, underline};
33
34#[cfg(feature = "tooltip")]
35use zng_wgt_tooltip::{Tip, TooltipArgs, tooltip, tooltip_fn};
36
37#[widget($crate::Button { ($cmd:expr) => { cmd = $cmd; }; })]
43pub struct Button(FocusableMix<StyleMix<Container>>);
44impl Button {
45 fn widget_intrinsic(&mut self) {
46 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
47
48 widget_set! {
49 self;
50 capture_pointer = true;
51 labelled_by_child = true;
52 txt_selectable_alt_only = true;
53 access_role = AccessRole::Button;
54 }
55 }
56
57 widget_impl! {
58 pub on_click(handler: Handler<ClickArgs>);
60
61 pub capture_pointer(mode: impl IntoVar<CaptureMode>);
65 }
66}
67impl_style_fn!(Button, DefaultStyle);
68
69context_var! {
70 pub static CMD_PARAM_VAR: Option<CommandParam> = None;
72
73 pub static CMD_CHILD_FN_VAR: WidgetFn<Command> = WidgetFn::new(default_cmd_child_fn);
75
76 #[cfg(feature = "tooltip")]
78 pub static CMD_TOOLTIP_FN_VAR: WidgetFn<CmdTooltipArgs> = WidgetFn::new(default_cmd_tooltip_fn);
79
80 static CMD_VAR: Option<Command> = None;
81}
82
83#[cfg(feature = "tooltip")]
84#[derive(Clone)]
88#[non_exhaustive]
89pub struct CmdTooltipArgs {
90 pub tooltip: TooltipArgs,
92 pub cmd: Command,
94}
95
96#[cfg(feature = "tooltip")]
97impl CmdTooltipArgs {
98 pub fn new(tooltip: TooltipArgs, cmd: Command) -> Self {
100 Self { tooltip, cmd }
101 }
102}
103#[cfg(feature = "tooltip")]
104impl std::ops::Deref for CmdTooltipArgs {
105 type Target = TooltipArgs;
106
107 fn deref(&self) -> &Self::Target {
108 &self.tooltip
109 }
110}
111
112pub fn default_cmd_child_fn(cmd: Command) -> UiNode {
114 Text!(cmd.name())
115}
116
117#[cfg(feature = "tooltip")]
118pub fn default_cmd_tooltip_fn(args: CmdTooltipArgs) -> UiNode {
120 let info = args.cmd.info();
121 let shortcut = args.cmd.shortcut();
122 let has_info = info.map(|s| !s.is_empty());
123 let has_shortcut = shortcut.map(|s| !s.is_empty());
124 Tip! {
125 child = zng_wgt_stack::Stack! {
126 direction = zng_wgt_stack::StackDirection::top_to_bottom();
127 spacing = 5;
128 children = ui_vec![
129 Text! {
130 zng_wgt::visibility = has_info.map_into();
131 txt = info;
132 },
133 zng_wgt_shortcut::ShortcutText! {
134 zng_wgt::visibility = has_shortcut.map_into();
135 shortcut;
136 },
137 ];
138 };
139 zng_wgt::visibility = expr_var!((*#{has_info} || *#{has_shortcut}).into());
140 }
141}
142
143#[property(CHILD, widget_impl(Button))]
161pub fn cmd(wgt: &mut WidgetBuilding, cmd: impl IntoVar<Command>) {
162 let cmd = cmd.into_var();
163
164 if wgt.property(property_id!(zng_wgt_container::child)).is_none() {
165 wgt.set_child(cmd.present(CMD_CHILD_FN_VAR));
166 }
167
168 let enabled = wgt.property(property_id!(zng_wgt::enabled)).is_none();
169 let visibility = wgt.property(property_id!(zng_wgt::visibility)).is_none();
170 wgt.push_intrinsic(
171 NestGroup::CONTEXT,
172 "cmd_context",
173 clmv!(cmd, |mut child| {
174 if enabled {
175 child = zng_wgt::enabled(child, cmd.flat_map(|c| c.is_enabled()));
176 }
177 if visibility {
178 child = zng_wgt::visibility(child, cmd.flat_map(|c| c.has_handlers()).map_into());
179 }
180
181 with_context_var(child, CMD_VAR, cmd.map(|c| Some(*c)))
182 }),
183 );
184
185 let on_click = wgt.property(property_id!(on_click)).is_none();
186 #[cfg(feature = "tooltip")]
187 let tooltip = wgt.property(property_id!(tooltip)).is_none() && wgt.property(property_id!(tooltip_fn)).is_none();
188 #[cfg(not(feature = "tooltip"))]
189 let tooltip = false;
190 if on_click || tooltip {
191 wgt.push_intrinsic(
192 NestGroup::EVENT,
193 "cmd_event",
194 clmv!(cmd, |mut child| {
195 if on_click {
196 child = self::on_click(
197 child,
198 hn!(cmd, |args| {
199 let cmd = cmd.get();
200 if cmd.is_enabled().get() {
201 if let Some(param) = CMD_PARAM_VAR.get() {
202 cmd.notify_param(param);
203 } else {
204 cmd.notify();
205 }
206 args.propagation.stop();
207 }
208 }),
209 );
210 }
211 #[cfg(feature = "tooltip")]
212 if tooltip {
213 child = self::tooltip_fn(
214 child,
215 merge_var!(cmd, CMD_TOOLTIP_FN_VAR, |cmd, tt_fn| {
216 if tt_fn.is_nil() {
217 WidgetFn::nil()
218 } else {
219 wgt_fn!(cmd, tt_fn, |tooltip| { tt_fn(CmdTooltipArgs { tooltip, cmd }) })
220 }
221 }),
222 );
223 }
224 child
225 }),
226 );
227 }
228}
229
230#[property(CONTEXT, default(CMD_PARAM_VAR), widget_impl(Button))]
236pub fn cmd_param<T: VarValue>(child: impl IntoUiNode, cmd_param: impl IntoVar<T>) -> UiNode {
237 if TypeId::of::<T>() == TypeId::of::<Option<CommandParam>>() {
238 with_context_var(
239 child,
240 CMD_PARAM_VAR,
241 AnyVar::from(cmd_param.into_var())
242 .downcast::<Option<CommandParam>>()
243 .unwrap_or_else(|_| unreachable!()),
244 )
245 } else {
246 with_context_var(
247 child,
248 CMD_PARAM_VAR,
249 cmd_param.into_var().map(|p| Some(CommandParam::new(p.clone()))),
250 )
251 }
252}
253
254#[property(CONTEXT, default(CMD_CHILD_FN_VAR), widget_impl(Button, DefaultStyle))]
259pub fn cmd_child_fn(child: impl IntoUiNode, cmd_child: impl IntoVar<WidgetFn<Command>>) -> UiNode {
260 with_context_var(child, CMD_CHILD_FN_VAR, cmd_child)
261}
262
263#[cfg(feature = "tooltip")]
264#[property(CONTEXT, default(CMD_TOOLTIP_FN_VAR), widget_impl(Button, DefaultStyle))]
268pub fn cmd_tooltip_fn(child: impl IntoUiNode, cmd_tooltip: impl IntoVar<WidgetFn<CmdTooltipArgs>>) -> UiNode {
269 with_context_var(child, CMD_TOOLTIP_FN_VAR, cmd_tooltip)
270}
271
272#[widget($crate::DefaultStyle)]
274pub struct DefaultStyle(Style);
275impl DefaultStyle {
276 fn widget_intrinsic(&mut self) {
277 widget_set! {
278 self;
279
280 replace = true;
281
282 padding = (7, 15);
283 corner_radius = 4;
284 child_align = Align::CENTER;
285
286 base_color = light_dark(rgb(0.82, 0.82, 0.82), rgb(0.18, 0.18, 0.18));
287
288 #[easing(150.ms())]
289 background_color = BASE_COLOR_VAR.rgba();
290 #[easing(150.ms())]
291 border = {
292 widths: 1,
293 sides: BASE_COLOR_VAR.rgba_into(),
294 };
295
296 when *#is_cap_hovered {
297 #[easing(0.ms())]
298 background_color = BASE_COLOR_VAR.shade(1);
299 #[easing(0.ms())]
300 border = {
301 widths: 1,
302 sides: BASE_COLOR_VAR.shade_into(2),
303 };
304 }
305
306 when *#is_pressed {
307 #[easing(0.ms())]
308 background_color = BASE_COLOR_VAR.shade(2);
309 }
310
311 when *#is_disabled {
312 saturate = false;
313 child_opacity = 50.pct();
314 cursor = CursorIcon::NotAllowed;
315 }
316 }
317 }
318}
319
320#[widget($crate::PrimaryStyle)]
322pub struct PrimaryStyle(DefaultStyle);
323impl_named_style_fn!(primary, PrimaryStyle);
324impl PrimaryStyle {
325 fn widget_intrinsic(&mut self) {
326 widget_set! {
327 self;
328 named_style_fn = PRIMARY_STYLE_FN_VAR;
329
330 base_color = ACCENT_COLOR_VAR.map(|c| c.shade(-2));
331 zng_wgt_text::font_weight = zng_ext_font::FontWeight::BOLD;
332 }
333 }
334}
335
336#[widget($crate::LightStyle)]
338pub struct LightStyle(DefaultStyle);
339impl_named_style_fn!(light, LightStyle);
340impl LightStyle {
341 fn widget_intrinsic(&mut self) {
342 widget_set! {
343 self;
344 named_style_fn = LIGHT_STYLE_FN_VAR;
345
346 border = unset!;
347 padding = 7;
348
349 #[easing(150.ms())]
350 background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(0.pct()));
351
352 when *#is_cap_hovered {
353 #[easing(0.ms())]
354 background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()));
355 }
356
357 when *#is_pressed {
358 #[easing(0.ms())]
359 background_color = FONT_COLOR_VAR.map(|c| c.with_alpha(20.pct()));
360 }
361
362 when *#is_disabled {
363 saturate = false;
364 child_opacity = 50.pct();
365 cursor = CursorIcon::NotAllowed;
366 }
367 }
368 }
369}
370
371#[widget($crate::LinkStyle)]
375pub struct LinkStyle(Style);
376impl_named_style_fn!(link, LinkStyle);
377impl LinkStyle {
378 fn widget_intrinsic(&mut self) {
379 widget_set! {
380 self;
381 replace = true;
382 named_style_fn = LINK_STYLE_FN_VAR;
383
384 font_color = light_dark(colors::BLUE, web_colors::LIGHT_BLUE);
385 cursor = CursorIcon::Pointer;
386 access_role = AccessRole::Link;
387
388 when *#is_cap_hovered {
389 underline = 1, LineStyle::Solid;
390 }
391
392 when *#is_pressed {
393 font_color = light_dark(web_colors::BROWN, colors::YELLOW);
394 }
395
396 when *#is_disabled {
397 saturate = false;
398 child_opacity = 50.pct();
399 cursor = CursorIcon::NotAllowed;
400 }
401 }
402 }
403}
404
405pub struct BUTTON;
407impl BUTTON {
408 pub fn cmd(&self) -> Var<Option<Command>> {
412 CMD_VAR.read_only()
413 }
414
415 pub fn cmd_param(&self) -> Var<Option<CommandParam>> {
419 CMD_PARAM_VAR.read_only()
420 }
421}