1use std::time::Duration;
2
3use super::unit::{DurationUnitExt, UnitExt};
4use super::*;
5use crate::style::Selectable;
6use crate::view::View;
7use crate::views::editor::SelectionColor;
8use crate::views::resizable::{ResizableClass, ResizableHandleClass};
9use crate::{
10 AnyView, prop, style_class, style_debug_group,
11 views::{
12 ButtonClass, CheckboxClass, LabelClass, LabelCustomExprStyle, LabelCustomStyle,
13 LabeledCheckboxClass, LabeledRadioButtonClass, ListClass, ListItemClass,
14 PlaceholderTextClass, RadioButtonClass, RadioButtonDotClass, SvgClass, TabSelectorClass,
15 TextInputClass, ToggleButtonCircleRad, ToggleButtonClass, ToggleButtonInset, TooltipClass,
16 dropdown,
17 resizable::{ResizableCustomExprStyle, ResizableCustomStyle},
18 scroll,
19 slider::{SliderClass, SliderCustomExprStyle, SliderCustomStyle},
20 },
21};
22use floem_renderer::text::FontWeight;
23use peniko::{Brush, Color, color::palette::css};
24use smallvec::smallvec;
25
26style_class!(pub HoverTargetClass);
27
28fn border_debug_view(style: &Style) -> Option<Box<dyn View>> {
29 Border {
30 left: style.get_prop::<BorderLeft>(),
31 top: style.get_prop::<BorderTop>(),
32 right: style.get_prop::<BorderRight>(),
33 bottom: style.get_prop::<BorderBottom>(),
34 }
35 .debug_view()
36}
37
38fn border_color_debug_view(style: &Style) -> Option<Box<dyn View>> {
39 BorderColor {
40 left: style.get_prop::<BorderLeftColor>().flatten(),
41 top: style.get_prop::<BorderTopColor>().flatten(),
42 right: style.get_prop::<BorderRightColor>().flatten(),
43 bottom: style.get_prop::<BorderBottomColor>().flatten(),
44 }
45 .debug_view()
46}
47
48fn border_radius_debug_view(style: &Style) -> Option<Box<dyn View>> {
49 BorderRadius {
50 top_left: style.get_prop::<BorderTopLeftRadius>(),
51 top_right: style.get_prop::<BorderTopRightRadius>(),
52 bottom_left: style.get_prop::<BorderBottomLeftRadius>(),
53 bottom_right: style.get_prop::<BorderBottomRightRadius>(),
54 }
55 .debug_view()
56}
57
58fn padding_debug_view(style: &Style) -> Option<Box<dyn View>> {
59 Padding {
60 left: style.get_prop::<PaddingLeft>(),
61 top: style.get_prop::<PaddingTop>(),
62 right: style.get_prop::<PaddingRight>(),
63 bottom: style.get_prop::<PaddingBottom>(),
64 }
65 .debug_view()
66}
67
68fn margin_debug_view(style: &Style) -> Option<Box<dyn View>> {
69 Margin {
70 left: style.get_prop::<MarginLeft>(),
71 top: style.get_prop::<MarginTop>(),
72 right: style.get_prop::<MarginRight>(),
73 bottom: style.get_prop::<MarginBottom>(),
74 }
75 .debug_view()
76}
77
78style_debug_group!(
79 pub BorderDebugGroup,
80 inherited = inherited,
81 members = [BorderLeft, BorderTop, BorderRight, BorderBottom],
82 view = border_debug_view
83);
84style_debug_group!(
85 pub BorderColorDebugGroup,
86 inherited = inherited,
87 members = [BorderLeftColor, BorderTopColor, BorderRightColor, BorderBottomColor],
88 view = border_color_debug_view
89);
90style_debug_group!(
91 pub BorderRadiusDebugGroup,
92 inherited = inherited,
93 members = [BorderTopLeftRadius, BorderTopRightRadius, BorderBottomLeftRadius, BorderBottomRightRadius],
94 view = border_radius_debug_view
95);
96style_debug_group!(
97 pub PaddingDebugGroup,
98 inherited = inherited,
99 members = [PaddingLeft, PaddingTop, PaddingRight, PaddingBottom],
100 view = padding_debug_view
101);
102style_debug_group!(
103 pub MarginDebugGroup,
104 inherited = inherited,
105 members = [MarginLeft, MarginTop, MarginRight, MarginBottom],
106 view = margin_debug_view
107);
108
109#[derive(Debug, Clone, PartialEq)]
110pub struct DesignSystem {
111 pub bg_base: Color,
112 pub text_base: Color,
113 pub text_lightness: f32,
114 pub primary_base: Color,
115 pub success_base: Color,
116 pub warning_base: Color,
117 pub danger_base: Color,
118 pub is_dark: bool,
119 pub padding: f32,
120 pub border_radius: f32,
121 pub font_size: f64,
122}
123impl DesignSystem {
127 pub fn light() -> Self {
129 Self {
130 bg_base: Color::from_rgb8(248, 248, 248),
131 text_base: Color::from_rgb8(0, 0, 0),
132 text_lightness: 0.05,
133 primary_base: Color::from_rgb8(0x18, 0x96, 0xC2),
134 success_base: Color::from_rgb8(0x2D, 0x9D, 0x67),
135 warning_base: Color::from_rgb8(0xE5, 0xA2, 0x23),
136 danger_base: Color::from_rgb8(0xD7, 0x37, 0x45),
137 padding: 5.,
138 border_radius: 5.,
139 font_size: 14.,
140 is_dark: false,
141 }
142 }
143
144 pub fn dark() -> Self {
146 Self {
147 bg_base: Color::from_rgb8(0x24, 0x24, 0x24),
148 text_base: Color::from_rgb8(255, 255, 255),
149 text_lightness: 0.95,
150 primary_base: Color::from_rgb8(0x3A, 0xAA, 0xD8),
151 success_base: Color::from_rgb8(0x4A, 0xBE, 0x8A),
152 warning_base: Color::from_rgb8(0xF5, 0xB8, 0x4E),
153 danger_base: Color::from_rgb8(0xF0, 0x56, 0x54),
154 padding: 5.,
155 border_radius: 5.,
156 font_size: 14.,
157 is_dark: true,
158 }
159 }
160
161 pub fn bg_base(&self) -> Color {
164 self.bg_base
165 }
166
167 pub fn bg_elevated(&self) -> Color {
168 let adjustment = 0.05;
169 self.bg_base.map_lightness(|l| l + adjustment)
170 }
171
172 pub fn bg_overlay(&self) -> Color {
173 let adjustment = 0.10;
174 self.bg_base.map_lightness(|l| l + adjustment)
175 }
176
177 pub fn bg_disabled(&self) -> Color {
178 let adjustment = if self.is_dark { -0.05 } else { -0.1 };
179 self.bg_base.map_lightness(|l| l + adjustment)
180 }
181
182 pub fn border(&self) -> Color {
185 let adjustment = if self.is_dark { 0.25 } else { -0.25 };
186 self.bg_base.map_lightness(|l| l + adjustment)
187 }
188
189 pub fn border_muted(&self) -> Color {
190 let adjustment = if self.is_dark { 0.15 } else { -0.15 };
191 self.border()
192 .map_lightness(|l| l + adjustment)
193 .with_alpha(0.8)
194 }
195
196 pub fn text(&self) -> Color {
199 self.text_base.map_lightness(|_| self.text_lightness)
200 }
201
202 pub fn text_muted(&self) -> Color {
203 let adjustment = if self.is_dark { -0.25 } else { 0.25 };
204 self.text_base
205 .map_lightness(|l| l + adjustment)
206 .with_alpha(0.5)
207 }
208
209 pub fn primary(&self) -> Color {
212 self.primary_base
213 }
214
215 pub fn primary_muted(&self) -> Color {
216 self.primary_base.map_lightness(|l| l - 0.05)
217 }
218
219 pub fn success(&self) -> Color {
222 self.success_base
223 }
224
225 pub fn warning(&self) -> Color {
226 self.warning_base
227 }
228
229 pub fn danger(&self) -> Color {
230 self.danger_base
231 }
232
233 pub fn info(&self) -> Color {
234 self.primary_base
235 }
236
237 pub fn padding(&self) -> f32 {
238 self.padding
239 }
240
241 pub fn border_radius(&self) -> f32 {
242 self.border_radius
243 }
244
245 pub fn font_size(&self) -> f64 {
246 self.font_size
247 }
248}
249
250impl StylePropValue for DesignSystem {
251 fn debug_view(&self) -> Option<AnyView> {
252 use crate::prelude::*;
253 use crate::views::Stack;
254
255 let design_system = self.clone();
256 let is_expanded = RwSignal::new(false);
257
258 let color_swatch = |label: &str, color: Color| {
259 Stack::new((
260 label.to_string().style(|s| s.width(120.0).font_size(12.0)),
261 color.debug_view().unwrap(),
262 ))
263 .style(|s| s.flex_row().items_center().gap(8.0).padding_vert(2.0))
264 };
265
266 let scalar_field = |label: &str, value: f64| {
267 Stack::new((
268 label.to_string().style(|s| s.width(120.0).font_size(12.0)),
269 format!("{:.2}", value).style(|s| s.font_size(12.0)),
270 ))
271 .style(|s| s.flex_row().items_center().gap(8.0).padding_vert(2.0))
272 };
273
274 let chevron = move || {
275 if is_expanded.get() {
276 svg(
277 r#"<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M4.427 6.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 6H4.604a.25.25 0 00-.177.427z"/></svg>"#,
278 )
279 } else {
280 svg(
281 r#"<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M6.427 4.427l3.396 3.396a.25.25 0 010 .354l-3.396 3.396A.25.25 0 016 11.396V4.604a.25.25 0 01.427-.177z"/></svg>"#,
282 )
283 }.style(|s| s.size_full().with_theme(|s, t| s.color(t.text())))
284 };
285
286 let header = Stack::new((
287 dyn_view(chevron)
288 .class(ButtonClass)
289 .style(|s| s.size(16.0, 16.0).padding(0.)),
290 "Design System"
291 .to_string()
292 .style(|s| s.font_size(14.0).font_weight(FontWeight::SEMI_BOLD)),
293 ))
294 .action(move || {
295 is_expanded.update(|v| *v = !*v);
296 })
297 .style(|s| {
298 s.flex_row()
299 .items_center()
300 .gap(8.0)
301 .cursor(CursorStyle::Pointer)
302 });
303
304 let content = Stack::new((
305 header,
306 Stack::new((
307 color_swatch("bg_base", design_system.bg_base),
308 color_swatch("text_base", design_system.text_base),
309 color_swatch("primary_base", design_system.primary_base),
310 color_swatch("success_base", design_system.success_base),
311 color_swatch("warning_base", design_system.warning_base),
312 color_swatch("danger_base", design_system.danger_base),
313 scalar_field("text_lightness", f64::from(design_system.text_lightness)),
314 scalar_field("padding", f64::from(design_system.padding)),
315 scalar_field("border_radius", f64::from(design_system.border_radius)),
316 scalar_field("font_size", design_system.font_size),
317 format!("is_dark: {}", design_system.is_dark).style(|s| s.font_size(12.0)),
318 ))
319 .style(move |s| s.flex_col().gap(4.0))
320 .clip()
321 .style(move |s| {
322 s.height_pct(100.)
323 .apply_if(!is_expanded.get(), |s| s.height_pct(0.))
324 .transition_height(Transition::ease_in_out(Duration::from_millis(200)))
325 }),
326 ))
327 .style(|s| {
328 s.flex_col()
331 .padding(8.0)
332 .border(1.)
333 .border_color(palette::css::WHITE.with_alpha(0.3))
334 .border_radius(6.0)
335 .min_width(280.0)
336 .min_height_pct(0.)
337 .flex_grow(0.)
338 .flex_shrink(1.)
339 });
340
341 Some(content.into_any())
342 }
343
344 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
345 use peniko::color::HueDirection;
346 let t = value as f32;
347 let inv_t = 1.0 - t;
348 let t64 = value;
349 let inv_t64 = 1.0 - t64;
350
351 Some(DesignSystem {
352 bg_base: self.bg_base.lerp(other.bg_base, t, HueDirection::default()),
353 text_base: self
354 .text_base
355 .lerp(other.text_base, t, HueDirection::default()),
356 text_lightness: self.text_lightness * inv_t + other.text_lightness * t,
357 primary_base: self
358 .primary_base
359 .lerp(other.primary_base, t, HueDirection::default()),
360 success_base: self
361 .success_base
362 .lerp(other.success_base, t, HueDirection::default()),
363 warning_base: self
364 .warning_base
365 .lerp(other.warning_base, t, HueDirection::default()),
366 danger_base: self
367 .danger_base
368 .lerp(other.danger_base, t, HueDirection::default()),
369 is_dark: if t < 0.5 { self.is_dark } else { other.is_dark },
370 padding: self.padding * inv_t + other.padding * t,
371 border_radius: self.border_radius * inv_t + other.border_radius * t,
372 font_size: self.font_size * inv_t64 + other.font_size * t64,
373 })
374 }
375}
376
377prop!(
378 pub Theme: DesignSystem { inherited } = DesignSystem::light()
379);
380
381#[derive(Clone, Copy)]
382pub struct ThemeExpr(pub(crate) ContextRef<Theme>);
383
384impl ThemeExpr {
385 pub fn def<T>(self, f: impl Fn(DesignSystem) -> T + 'static) -> ContextValue<T>
386 where
387 T: 'static,
388 {
389 self.0.def(f)
390 }
391
392 pub fn bg_base(self) -> ContextValue<Color> {
393 self.def(|t| t.bg_base())
394 }
395 pub fn bg_elevated(self) -> ContextValue<Color> {
396 self.def(|t| t.bg_elevated())
397 }
398 pub fn bg_overlay(self) -> ContextValue<Color> {
399 self.def(|t| t.bg_overlay())
400 }
401 pub fn bg_disabled(self) -> ContextValue<Color> {
402 self.def(|t| t.bg_disabled())
403 }
404 pub fn border(self) -> ContextValue<Color> {
405 self.def(|t| t.border())
406 }
407 pub fn border_muted(self) -> ContextValue<Color> {
408 self.def(|t| t.border_muted())
409 }
410 pub fn text(self) -> ContextValue<Color> {
411 self.def(|t| t.text())
412 }
413 pub fn text_muted(self) -> ContextValue<Color> {
414 self.def(|t| t.text_muted())
415 }
416 pub fn primary(self) -> ContextValue<Color> {
417 self.def(|t| t.primary())
418 }
419 pub fn primary_muted(self) -> ContextValue<Color> {
420 self.def(|t| t.primary_muted())
421 }
422 pub fn success(self) -> ContextValue<Color> {
423 self.def(|t| t.success())
424 }
425 pub fn warning(self) -> ContextValue<Color> {
426 self.def(|t| t.warning())
427 }
428 pub fn danger(self) -> ContextValue<Color> {
429 self.def(|t| t.danger())
430 }
431 pub fn info(self) -> ContextValue<Color> {
432 self.def(|t| t.info())
433 }
434 pub fn padding(self) -> ContextValue<Length> {
435 self.def(|t| t.padding().into())
436 }
437 pub fn border_radius(self) -> ContextValue<Length> {
438 self.def(|t| t.border_radius().into())
439 }
440 pub fn font_size(self) -> ContextValue<f64> {
441 self.def(|t| t.font_size())
442 }
443 pub fn is_dark(self) -> ContextValue<bool> {
444 self.def(|t| t.is_dark)
445 }
446 pub fn warning_base(self) -> ContextValue<Color> {
447 self.def(|t| t.warning_base)
448 }
449}
450
451pub trait StyleThemeExt {
452 fn theme(self, theme: DesignSystem) -> Self;
453 fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self
454 where
455 Self: std::marker::Sized;
456}
457
458impl StyleThemeExt for Style {
459 fn theme(self, theme: DesignSystem) -> Self {
460 self.set(Theme, theme)
461 }
462 fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self {
463 self.with::<Theme>(|s, t| f(s, ThemeExpr(t)))
464 }
465}
466
467impl StyleThemeExt for ExprStyle {
468 fn theme(self, theme: DesignSystem) -> Self {
469 self.set(Theme, theme)
470 }
471 fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self {
472 self.with::<Theme>(|s, t| f(s, ThemeExpr(t)))
473 }
474}
475
476pub fn hover_style() -> Style {
480 Style::new().hover(|s| s.with_theme(|s, t| s.background(t.def(|t| t.bg_elevated()))))
481}
482
483pub fn focus_style() -> Style {
484 let focus_visible_applied_style = Style::new().outline(3.0);
485
486 Style::new()
487 .keyboard_navigable()
488 .with::<Theme>(|s, t| s.outline_color(t.def(|t| t.primary().with_alpha(0.5))))
489 .focus_visible(|_| focus_visible_applied_style.clone())
490}
491
492pub fn border_style(with_radius: bool) -> Style {
493 Style::new()
494 .with_theme(move |s, t| {
495 s.border_color(t.border())
496 .disabled(|s| s.border_color(t.border()))
497 .padding(t.padding())
498 .apply_if(with_radius, |s| s.border_radius(t.border_radius()))
499 })
500 .border(1.0)
501}
502
503pub fn item_selected_style() -> Style {
504 Style::new().selected(|s| {
505 s.with_theme(|s, t| {
506 s.background(t.primary())
507 .color(t.bg_base())
508 .hover(|s| s.background(t.primary_muted()))
509 })
510 .transition_background(Transition::linear(100.millis()))
511 })
512}
513
514pub fn overlay_style() -> Style {
515 Style::new()
516 .with_theme(|s, t| {
517 let shadow_color = Color::from_rgb8(0, 0, 0);
518 s.border_color(t.border())
519 .border_radius(t.border_radius())
520 .padding(t.padding())
521 .color(t.text())
522 .background(t.bg_overlay())
523 .set_context(
524 BoxShadowProp,
525 t.def(move |theme| {
526 let base_opacity = if theme.is_dark { 0.7 } else { 0.18 };
527 smallvec![
528 BoxShadow::new()
529 .color(shadow_color.with_alpha(base_opacity * 1.2))
530 .v_offset(1.)
531 .blur_radius(2.)
532 .spread(0.),
533 BoxShadow::new()
534 .color(shadow_color.with_alpha(base_opacity * 0.8))
535 .v_offset(4.)
536 .blur_radius(8.)
537 .spread(-1.),
538 BoxShadow::new()
539 .color(shadow_color.with_alpha(base_opacity * 0.5))
540 .v_offset(12.)
541 .blur_radius(24.)
542 .spread(-4.),
543 ]
544 }),
545 )
546 })
547 .dark_mode(|s| s.border(1).border_top(2.))
548}
549
550pub(crate) fn default_theme(os_theme: winit::window::Theme) -> Style {
551 let button_style = Style::new()
552 .selectable(false)
553 .with_theme(|s, t| {
554 s.background(t.bg_elevated())
555 .padding(t.padding())
556 .disabled(|s| {
557 s.background(t.bg_disabled())
558 .color(t.text_muted())
559 .unset_cursor()
560 })
561 .hover(|s| s.background(t.bg_overlay()))
562 .active(move |s| {
563 s.background(t.def(|theme| {
564 let adjustment = if theme.is_dark { 0.1 } else { -0.2 };
565 Brush::Solid(theme.bg_overlay().map_lightness(|l| l + adjustment))
566 }))
567 })
568 })
569 .transition(Background, Transition::linear(100.millis()))
570 .justify_center()
571 .items_center()
572 .cursor(CursorStyle::Pointer)
573 .apply(focus_style())
574 .apply(border_style(true));
575
576 let checkbox_style = Style::new()
577 .size(20, 20)
578 .with_theme(|s, t| {
579 s.background(t.bg_base())
580 .active(|s| s.background(t.bg_elevated()))
581 .disabled(|s| {
582 s.background(t.def(|t| t.bg_elevated().with_alpha(0.3)))
583 .color(t.text_muted())
584 .unset_cursor()
585 })
586 })
587 .transition(Background, Transition::linear(100.millis()))
588 .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
589 .cursor(CursorStyle::Pointer)
590 .apply(border_style(true))
591 .apply(hover_style())
592 .apply(focus_style());
593
594 let labeled_checkbox_style = Style::new()
595 .with_theme(|s, t| {
596 s.hover(|s| s.background(t.def(|t| t.primary_muted().with_alpha(0.7))))
597 .col_gap(t.padding())
598 .padding(t.padding())
599 .border_radius(t.border_radius())
600 .active(|s| {
601 s.class(CheckboxClass, |s| s.background(t.primary()))
602 .background(t.primary())
603 })
604 .disabled(|s| {
605 s.unset_cursor()
606 .color(t.text_muted())
607 .class(CheckboxClass, |s| {
608 s.background(t.bg_disabled())
609 .color(t.text_muted())
610 .hover(|s| s.background(t.def(|t| t.bg_elevated().with_alpha(0.3))))
611 })
612 })
613 })
614 .cursor(CursorStyle::Pointer)
615 .transition(Background, Transition::linear(100.millis()))
616 .class(CheckboxClass, |s| s.focus_none())
617 .selectable(false)
618 .focus(|s| {
619 s.class(CheckboxClass, |s| {
620 s.with_theme(|s, t| s.border_color(t.primary()))
621 })
622 .with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay())))
623 })
624 .apply(hover_style())
625 .apply(focus_style());
626
627 let radio_button_style = Style::new()
628 .size(20, 20)
629 .items_center()
630 .justify_center()
631 .with_theme(|s, t| {
632 s.background(t.bg_base())
633 .active(|s| s.background(t.bg_base()))
634 .hover(|s| s.background(t.bg_elevated()))
635 .disabled(|s| {
636 s.background(t.bg_disabled())
637 .color(t.text_muted())
638 .unset_cursor()
639 })
640 })
641 .cursor(CursorStyle::Pointer)
642 .transition(Background, Transition::linear(100.millis()))
643 .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
644 .border_radius(100.pct())
645 .flex_shrink(0.)
646 .apply(border_style(false))
647 .apply(focus_style());
648
649 let radio_button_dot_style = Style::new()
650 .size(8, 8)
651 .border_radius(100.0)
652 .with_theme(|s, t| {
653 s.background(t.text()).disabled(|s| {
654 s.background(t.text_muted())
655 .hover(|s| s.background(t.text_muted()))
656 })
657 });
658
659 let labeled_radio_button_style = Style::new()
660 .with_theme(move |s, t| {
661 s.col_gap(t.padding())
662 .padding(t.padding())
663 .set(Selectable, false)
664 .border_radius(t.border_radius())
665 .hover(|s| s.background(t.def(|t| t.primary_muted().with_alpha(0.7))))
666 .active(|s| s.class(RadioButtonClass, |s| s.background(t.bg_elevated())))
667 .selected(|s| s.disabled(|s| s.color(t.bg_elevated())))
668 .disabled(|s| s.color(t.text_muted()).unset_cursor())
669 })
670 .cursor(CursorStyle::Pointer)
671 .class(RadioButtonClass, |s| s.focus_none())
672 .transition(Background, Transition::linear(100.millis()))
673 .focus(|s| {
674 s.with_theme(|s, t| s.hover(|s| s.background(t.def(|t| t.primary().with_alpha(0.7)))))
675 });
676
677 let toggle_button_style = Style::new()
678 .height(1.75.em())
679 .with_theme(|s, t| {
680 s.background(t.bg_elevated())
681 .padding(t.padding())
682 .set_context_opt(Foreground, t.def(|t| Some(Brush::Solid(t.text_muted()))))
683 .active(|s| {
684 s.background(t.primary())
685 .color(t.bg_base())
686 .set_context_opt(Foreground, t.def(|t| Some(Brush::Solid(t.bg_base()))))
687 })
688 .hover(|s| s.background(t.bg_overlay()))
689 })
690 .aspect_ratio(2.)
691 .border_radius(50.pct())
693 .set(ToggleButtonCircleRad, 75.pct())
694 .set(ToggleButtonInset, 10.pct())
695 .apply(border_style(false))
696 .apply(focus_style());
697
698 let input_style = Style::new()
699 .with_theme(|s, t| {
700 s.background(t.bg_base())
701 .padding(t.padding())
702 .set_context(
703 SelectionColor,
704 t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))),
705 )
706 .cursor_color(t.primary_muted())
707 .hover(|s| s.background(t.bg_elevated()))
708 .disabled(|s| {
709 s.background(t.bg_disabled())
710 .color(t.text_muted())
711 .unset_cursor()
712 })
713 })
714 .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
715 .apply(border_style(true))
716 .apply(focus_style())
717 .cursor(CursorStyle::Text);
718
719 let tab_selector_style = Style::new()
720 .custom_style_class(|s: LabelCustomStyle| s.selectable(false))
721 .with_theme(|s, t| {
722 s.background(t.bg_base())
723 .padding(t.padding())
724 .color(t.text_muted())
725 .border_bottom(2.)
726 .disabled(|s| s.background(t.bg_disabled()).color(t.text_muted()))
727 .selected(|s| {
728 s.background(t.bg_elevated())
729 .color(t.text())
730 .border_color(t.primary())
731 })
732 .hover(|s| s.background(t.bg_elevated()).color(t.text()))
733 })
734 .border_color(Color::TRANSPARENT)
735 .transition(Background, Transition::linear(100.millis()))
736 .transition(Foreground, Transition::linear(100.millis()))
737 .justify_center()
738 .items_center()
739 .text_clip()
740 .selectable(false)
741 .apply(focus_style())
742 .apply(hover_style());
743
744 Style::new()
752 .debug_group(BorderDebugGroup)
753 .debug_group(BorderColorDebugGroup)
754 .debug_group(BorderRadiusDebugGroup)
755 .debug_group(PaddingDebugGroup)
756 .debug_group(MarginDebugGroup)
757 .apply_if(os_theme == winit::window::Theme::Light, |s| {
758 let light = DesignSystem::light();
759 s.color(light.text())
760 .font_size(light.font_size())
761 .background(light.bg_base())
762 .color(light.text())
763 .theme(light)
764 })
765 .apply_if(os_theme == winit::window::Theme::Dark, |s| {
766 let dark = DesignSystem::dark();
767 s.color(dark.text())
768 .font_size(dark.font_size())
769 .background(dark.bg_base())
770 .color(dark.text())
771 .theme(dark)
772 })
773 .line_height(1.2)
774 .class(LabelClass, |s| {
775 s.with_theme(|s, t| {
776 s.custom(|s: LabelCustomExprStyle| {
777 s.selection_color(t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))))
778 })
779 })
780 .with::<Selectable>(|s, selectable| {
781 s.set_context_opt(
782 Cursor,
783 selectable.def(|selectable| {
784 if selectable {
785 Some(CursorStyle::Text)
786 } else {
787 None
788 }
789 }),
790 )
791 })
792 .focusable()
793 })
794 .class(ListClass, |s| {
795 s.apply(focus_style()).class(ListItemClass, |s| {
796 s.with_theme(|s, t| {
797 s.hover(|s| s.background(t.bg_elevated())).selected(|s| {
798 s.background(t.primary())
799 .color(t.bg_base())
800 .hover(|s| s.background(t.primary_muted()))
801 .transition_background(Transition::linear(100.millis()))
802 })
803 })
804 .with_theme(|s, t| s.border_radius(t.border_radius()).padding_left(t.padding()))
805 .items_center()
806 })
807 })
808 .class(CheckboxClass, |_| checkbox_style)
809 .class(LabeledCheckboxClass, |_| labeled_checkbox_style)
810 .class(RadioButtonClass, |_| radio_button_style)
811 .class(RadioButtonDotClass, |_| radio_button_dot_style)
812 .class(LabeledRadioButtonClass, |_| labeled_radio_button_style)
813 .class(TextInputClass, |_| input_style)
814 .class(ButtonClass, |_| button_style)
815 .class(TabSelectorClass, |_| tab_selector_style)
816 .custom_style_class(|s: scroll::ScrollCustomStyle| {
817 s.handle_border_radius(4.0)
818 .handle_rounded(false)
819 .apply_if(cfg!(target_os = "macos"), |s| s.handle_rounded(true))
820 })
821 .class(scroll::Handle, |s| {
822 s.with_theme(|s, t| {
823 s.background(t.border())
824 .active(|s| s.background(t.text_muted()))
825 .hover(|s| s.background(t.text_muted()))
826 })
827 .transition_background(Transition::ease_in_out(Duration::from_millis(300)))
828 })
829 .class(scroll::Track, |s| {
830 s.with_theme(|s, t| s.hover(|s| s.background(t.def(|t| t.border().with_alpha(0.3)))))
831 .background(css::TRANSPARENT)
832 .transition_background(Transition::ease_in_out(Duration::from_millis(300)))
833 })
834 .class(ToggleButtonClass, |_| toggle_button_style)
835 .class(SliderClass, |s| {
836 s.apply(focus_style())
837 .custom(|cs: SliderCustomStyle| {
838 cs.bar_radius(100.pct())
839 .accent_bar_radius(100.pct())
840 .handle_radius(100.pct())
841 .edge_align(true)
842 })
843 .with_theme(|s, t| {
844 s.custom(|cs: SliderCustomExprStyle| {
845 cs.bar_color(t.def(|t| Some(Brush::Solid(t.border()))))
846 .accent_bar_color(t.def(|t| Brush::Solid(t.primary())))
847 .handle_color(t.def(|t| Some(Brush::Solid(t.text()))))
848 })
849 })
850 })
851 .class(PlaceholderTextClass, |s| {
852 s.with_theme(|s, t| {
853 s.color(t.text_muted()).disabled(|s| {
854 s.color(t.def(|t| t.text_muted().with_alpha(0.5)))
855 .set(Background, Some(Brush::Solid(css::BLACK)))
856 })
857 })
858 })
859 .class(TooltipClass, |s| s.apply(overlay_style()))
860 .class(dropdown::DropdownClass, move |s| {
861 s.padding(3)
862 .apply(focus_style())
863 .apply(border_style(true))
864 .selectable(false)
865 .class(dropdown::DropdownPreviewClass, |s| {
866 s.gap(0.75.em()).class(SvgClass, |s| {
867 s.with_theme(|s, t| {
868 s.hover(|s| s.background(t.bg_elevated()))
869 .border_radius(t.border_radius())
870 .color(t.text())
871 })
872 .padding(5.)
873 .size(1.em(), 1.em())
874 })
875 })
876 .class(scroll::ScrollClass, move |s| {
877 s.width_full()
878 .scrollbar_width(0)
879 .margin_top(3)
880 .padding_vert(3)
881 .apply(overlay_style())
882 .items_center()
883 .class(ListItemClass, move |s| {
884 s.padding(6).with_theme(|s, t| {
885 s.hover(|s| {
886 s.background(t.bg_elevated())
887 .selected(|s| s.background(t.primary_muted()))
888 })
889 })
890 })
891 })
892 })
893 .class(ResizableClass, |s| s.padding_right(3))
894 .class(ResizableHandleClass, |s| {
895 s.custom(|cs: ResizableCustomStyle| cs.handle_thickness(3.))
896 .with_theme(|s, t| {
897 s.custom(|cs: ResizableCustomExprStyle| {
898 cs.handle_color(t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))))
899 .hover(|s| s.handle_color(t.def(|t| Brush::Solid(t.primary()))))
900 })
901 })
902 })
903 .class(HoverTargetClass, |s| {
904 s.with_theme(|s, t| {
905 s.padding(t.padding())
906 .border_radius(t.border_radius())
907 .background(t.bg_elevated())
908 .outline(3)
909 .file_hover(|s| s.background(t.bg_overlay()).outline_color(t.primary()))
910 })
911 .cursor(CursorStyle::Pointer)
912 .transition(Background, Transition::linear(100.millis()))
913 })
914}