Skip to main content

makara/widgets/
mod.rs

1//! Collection of built-in widgets provided by Makara.
2
3pub mod button;
4pub mod text;
5pub mod row;
6pub mod column;
7pub mod modal;
8pub mod checkbox;
9pub mod root;
10pub mod radio;
11pub mod dropdown;
12pub mod select;
13pub mod link;
14pub mod slider;
15pub mod progress_bar;
16pub mod circular;
17pub mod tooltip;
18pub mod text_input;
19pub mod text_edit;
20pub mod scroll;
21pub mod image;
22
23pub use button::*;
24pub use text::*;
25pub use row::*;
26pub use column::*;
27pub use modal::*;
28pub use checkbox::*;
29pub use root::*;
30pub use radio::*;
31pub use dropdown::*;
32pub use select::*;
33pub use link::*;
34pub use slider::*;
35pub use progress_bar::*;
36pub use circular::*;
37pub use tooltip::*;
38pub use text_input::*;
39pub use text_edit::*;
40pub use scroll::*;
41pub use image::*;
42
43use bevy::prelude::*;
44use bevy::window::{CursorIcon, SystemCursorIcon};
45use bevy::ecs::query::QueryFilter;
46use bevy::ecs::system::SystemParam;
47use cosmic_text::{FontSystem, SwashCache, Attrs};
48use crate::{events::*, colors::IntoColor};
49
50pub trait Widget {
51    fn build(self) -> impl Bundle;
52}
53
54/// Trait for setting tooltip and its style.
55pub trait SetToolTip: Sized {
56    fn set_tooltip(&mut self) -> &mut TooltipBundle;
57
58    /// Enable and set tooltip set.
59    fn tooltip(mut self, text: &str) -> Self {
60        self.set_tooltip().use_tooltip = UseTooltip(true);
61        self.set_tooltip().text = TooltipText(text.to_string());
62        self.set_tooltip().text_bundle.text.0 = text.to_string();
63        self
64    }
65
66    /// Set tooltip background style.
67    fn tooltip_style(mut self, style: ContainerStyle) -> Self {
68        self.set_tooltip().style = style;
69        self
70    }
71
72    /// Set tooltip text style.
73    fn tooltip_text_style(mut self, text_style: TextStyle) -> Self {
74        self.set_tooltip().text_bundle.text_style = text_style;
75        self
76    }
77
78    /// Set tooltip position.
79    /// Available positions: `left`, `right`, `top`, `bottom`, `center`.
80    /// Default is `center`.
81    fn tooltip_position(mut self, pos: &str) -> Self {
82        match pos {
83            "left" => self.set_tooltip().position = TooltipPosition::Left,
84            "right" => self.set_tooltip().position = TooltipPosition::Right,
85            "top" => self.set_tooltip().position = TooltipPosition::Top,
86            "bottom" => self.set_tooltip().position = TooltipPosition::Bottom,
87            _ => self.set_tooltip().position = TooltipPosition::Center,
88        }
89        self
90    }
91}
92
93pub trait SetIdAndClass: Sized {
94    fn id_and_class(&mut self) -> &mut IdAndClass;
95
96    fn id(mut self, id: &str) -> Self {
97        self.id_and_class().id.0 = id.to_string();
98        self
99    }
100
101    fn class(mut self, class: &str) -> Self {
102        self.id_and_class().class.0 = class.to_string();
103        self
104    }
105}
106
107pub trait SetContainerStyle: Sized {
108    fn container_style(&mut self) -> &mut ContainerStyle;
109
110    fn node(mut self, node: Node) -> Self {
111        self.container_style().node = node;
112        self
113    }
114
115    fn width(mut self, value: Val) -> Self {
116        self.container_style().node.width = value;
117        self
118    }
119
120    fn height(mut self, value: Val) -> Self {
121        self.container_style().node.height = value;
122        self
123    }
124
125    fn align_items(mut self, ai: AlignItems) -> Self {
126        self.container_style().node.align_items = ai;
127        self
128    }
129
130    fn align_content(mut self, ac: AlignContent) -> Self {
131        self.container_style().node.align_content = ac;
132        self
133    }
134
135    fn justify_content(mut self, jc: JustifyContent) -> Self {
136        self.container_style().node.justify_content = jc;
137        self
138    }
139
140    fn margin(mut self, value: Val) -> Self {
141        self.container_style().node.margin = UiRect::all(value);
142        self
143    }
144
145    fn margin_x(mut self, value: Val) -> Self {
146        self.container_style().node.margin.left = value;
147        self.container_style().node.margin.right = value;
148        self
149    }
150
151    fn margin_y(mut self, value: Val) -> Self {
152        self.container_style().node.margin.top = value;
153        self.container_style().node.margin.bottom = value;
154        self
155    }
156
157    fn margin_top(mut self, value: Val) -> Self {
158        self.container_style().node.margin.top = value;
159        self
160    }
161
162    fn margin_right(mut self, value: Val) -> Self {
163        self.container_style().node.margin.right = value;
164        self
165    }
166
167    fn margin_bottom(mut self, value: Val) -> Self {
168        self.container_style().node.margin.bottom = value;
169        self
170    }
171
172    fn margin_left(mut self, value: Val) -> Self {
173        self.container_style().node.margin.left = value;
174        self
175    }
176
177    fn padding(mut self, value: Val) -> Self {
178        self.container_style().node.padding = UiRect::all(value);
179        self
180    }
181
182    fn padding_x(mut self, value: Val) -> Self {
183        self.container_style().node.padding.left = value;
184        self.container_style().node.padding.right = value;
185        self
186    }
187
188    fn padding_y(mut self, value: Val) -> Self {
189        self.container_style().node.padding.top = value;
190        self.container_style().node.padding.bottom = value;
191        self
192    }
193
194    fn padding_top(mut self, value: Val) -> Self {
195        self.container_style().node.padding.top = value;
196        self
197    }
198
199    fn padding_bottom(mut self, value: Val) -> Self {
200        self.container_style().node.padding.bottom = value;
201        self
202    }
203
204    fn padding_left(mut self, value: Val) -> Self {
205        self.container_style().node.padding.left = value;
206        self
207    }
208
209    fn padding_right(mut self, value: Val) -> Self {
210        self.container_style().node.padding.right = value;
211        self
212    }
213
214    fn border(mut self, value: Val) -> Self {
215        self.container_style().node.border = UiRect::all(value);
216        self
217    }
218
219    fn border_top(mut self, value: Val) -> Self {
220        self.container_style().node.border.top = value;
221        self
222    }
223
224    fn border_bottom(mut self, value: Val) -> Self {
225        self.container_style().node.border.bottom = value;
226        self
227    }
228
229    fn border_left(mut self, value: Val) -> Self {
230        self.container_style().node.border.left = value;
231        self
232    }
233
234    fn border_right(mut self, value: Val) -> Self {
235        self.container_style().node.border.right = value;
236        self
237    }
238
239    fn background_color(mut self, color: impl IntoColor) -> Self {
240        self.container_style().background_color.0 = color.into_color();
241        self
242    }
243
244    fn border_radius(mut self, radius: BorderRadius) -> Self{
245        self.container_style().node.border_radius = radius;
246        self
247    }
248
249    fn border_color(mut self, color: impl IntoColor) -> Self {
250        self.container_style().border_color = BorderColor::all(color.into_color());
251        self
252    }
253
254    fn border_top_color(mut self, color: impl IntoColor) -> Self {
255        self.container_style().border_color.top = color.into_color();
256        self
257    }
258
259    fn border_bottom_color(mut self, color: impl IntoColor) -> Self {
260        self.container_style().border_color.bottom = color.into_color();
261        self
262    }
263
264    fn border_left_color(mut self, color: impl IntoColor) -> Self {
265        self.container_style().border_color.left = color.into_color();
266        self
267    }
268
269    fn border_right_color(mut self, color: impl IntoColor) -> Self {
270        self.container_style().border_color.right = color.into_color();
271        self
272    }
273
274    fn shadow(mut self, shadow: ShadowStyle) -> Self {
275        self.container_style().shadow = BoxShadow(vec![shadow]);
276        self
277    }
278
279    fn no_shadow(mut self) -> Self {
280        self.container_style().shadow = BoxShadow::default();
281        self
282    }
283
284    fn style(mut self, style: ContainerStyle) -> Self {
285        *self.container_style() = style;
286        self
287    }
288}
289
290/// Component used to store id for a widget.
291#[derive(Component, Debug, Default, PartialEq, Eq, Clone)]
292pub struct Id(pub String);
293
294/// Component used to store class for a widget.
295#[derive(Component, Debug, Default, PartialEq, Eq, Clone)]
296pub struct Class(pub String);
297
298impl Class {
299    /// Adds a class (or multiple space-separated classes) to the widget.
300    pub fn add_class(&mut self, class: &str) {
301        let mut classes: Vec<_> = self.0.split_whitespace().collect();
302
303        for new_class in class.split_whitespace() {
304            if !classes.contains(&new_class) {
305                classes.push(new_class);
306            }
307        }
308
309        self.0 = classes.join(" ");
310    }
311
312    /// Removes a specific class (or multiple) from the widget.
313    pub fn remove_class(&mut self, class: &str) {
314        let to_remove: Vec<_> = class.split_whitespace().collect();
315
316        let classes: Vec<_> = self.0
317            .split_whitespace()
318            .filter(|c| !to_remove.contains(c))
319            .collect();
320
321        self.0 = classes.join(" ");
322    }
323
324    /// Returns an iterator over the individual class names.
325    ///
326    /// Useful for checking specific styles or logic conditions.
327    pub fn class_list(&self) -> Vec<String> {
328        self.0
329            .split_whitespace()
330            .map(|s| s.to_string())
331            .collect()
332    }
333
334    /// Checks if the widget contains a specific class.
335    pub fn has_class(&self, class: &str) -> bool {
336        self.0.split_whitespace().any(|c| c == class)
337    }
338}
339
340#[derive(Bundle, Clone, Default, Debug)]
341pub struct IdAndClass {
342    pub id: Id,
343    pub class: Class
344}
345
346/// System param for based styling for all widgets.
347#[derive(SystemParam)]
348pub struct StyleQuery<'w, 's, F: QueryFilter + 'static = ()> {
349    pub query: Query<'w, 's, (
350        &'static mut Node,
351        &'static mut BackgroundColor,
352        &'static mut BorderColor,
353        &'static mut BoxShadow,
354        &'static mut ZIndex,
355    ), F>,
356}
357
358/// A struct used to mutate styling components for text.
359/// This struct is only used within other widgets.
360pub struct ChildText<'a> {
361    pub value: &'a mut Text,
362    pub font: &'a mut TextFont,
363    pub layout: &'a mut TextLayout,
364    pub color: &'a mut TextColor
365}
366
367/// A struct used to mutate styling components for widgets.
368pub struct WidgetStyle<'a> {
369    pub node: &'a mut Node,
370    pub background_color: &'a mut BackgroundColor,
371    pub border_color: &'a mut BorderColor,
372    pub z_index: &'a mut ZIndex,
373    pub shadow: &'a mut BoxShadow
374}
375
376/// Trait used for widgets system param.
377pub trait WidgetQuery<'w, 's> {
378    /// The specific view struct this query returns (Ex, ButtonWidget, TextWidget).
379    type WidgetView<'a> where Self: 'a;
380
381    /// Method to find widget by ID.
382    fn find_by_id<'a>(&'a mut self, target_id: &str) -> Option<Self::WidgetView<'a>>;
383
384    /// Method to find widget by Entity.
385    fn find_by_entity<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>>;
386
387    /// Method to find entities that match with provided classes.
388    fn find_by_class(&self, target_class: &str) -> Vec<Entity>;
389
390    // /// Get the widget and executes a closure on a widget found by its ID.
391    // ///
392    // /// This is the preferred way to perform single-shot updates to avoid `if let` boilerplate.
393    // ///
394    // /// # Example
395    // /// ```rust
396    // /// text_q.get_by_id("header", |t| t.text.value.0 = "Settings".to_string());
397    // /// ```
398    // fn get_by_id(&mut self, id: &str, f: impl FnOnce(&mut Self::WidgetView<'_>)) {
399    //     if let Some(mut widget) = self.find_by_id(id) {
400    //         f(&mut widget);
401    //     }
402    // }
403
404    // /// Get the widget and executes a closure on a widget found by its Entity.
405    // fn get_by_entity(&mut self, entity: Entity, f: impl FnOnce(&mut Self::WidgetView<'_>)) {
406    //     if let Some(mut widget) = self.find_by_entity(entity) {
407    //         f(&mut widget);
408    //     }
409    // }
410
411    // /// Executes a closure on every widget that matches the specified class.
412    // ///
413    // /// This is useful for bulk-updating styles or states (e.g., highlighting all buttons).
414    // ///
415    // /// # Example
416    // /// ```rust
417    // /// button_q.get_by_class("active", |btn| btn.style.background_color.0 = Color::GOLD);
418    // /// ```
419    // fn get_by_class(&mut self, class: &str, mut f: impl FnMut(&mut Self::WidgetView<'_>)) {
420    //     let entities = self.find_by_class(class);
421
422    //     for entity in entities {
423    //         if let Some(mut widget) = self.find_by_entity(entity) {
424    //             f(&mut widget);
425    //         }
426    //     }
427    // }
428
429    /// Get the widget and executes a closure on a widget found by its ID.
430    fn get_by_id<F>(&mut self, id: &str, f: F)
431    where
432        F: for<'a> FnOnce(&mut Self::WidgetView<'a>)
433    {
434        if let Some(mut widget) = self.find_by_id(id) {
435            f(&mut widget);
436        }
437    }
438
439    /// Get the widget and executes a closure on a widget found by its Entity.
440    fn get_by_entity<F>(&mut self, entity: Entity, f: F)
441    where
442        F: for<'a> FnOnce(&mut Self::WidgetView<'a>)
443    {
444        if let Some(mut widget) = self.find_by_entity(entity) {
445            f(&mut widget);
446        }
447    }
448
449    /// Executes a closure on every widget that matches the specified class.
450    fn get_by_class<F>(&mut self, class: &str, mut f: F)
451    where
452        F: for<'a> FnMut(&mut Self::WidgetView<'a>)
453    {
454        let entities = self.find_by_class(class);
455
456        for entity in entities {
457            if let Some(mut widget) = self.find_by_entity(entity) {
458                f(&mut widget);
459            }
460        }
461    }
462
463    /// Get related components of an entity.
464    fn get_components<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>>;
465}
466
467/// Trait used for widgets that accept child/children.
468pub trait WidgetChildren {
469    /// Add a single child to a widget.
470    fn add_child(&mut self, child_bundle: impl Bundle);
471
472    /// Add multiple children to a widget.
473    fn add_children(&mut self, bundles: impl IntoIterator<Item = impl Bundle>);
474
475    /// Insert children at given index.
476    fn insert_at(
477        &mut self,
478        index: usize,
479        bundles: impl IntoIterator<Item = impl Bundle>
480    );
481
482    /// Insert children at the beginning.
483    fn insert_first(&mut self, bundles: impl IntoIterator<Item = impl Bundle>);
484
485    /// Insert children at the end.
486    fn insert_last(&mut self, bundles: impl IntoIterator<Item = impl Bundle>);
487
488    /// Remove a child at given index. Does nothing if index out of bound.
489    /// This will remove the child from hierachy and UI world.
490    fn remove_at(&mut self, index: usize);
491
492    /// Remove the first child.
493    /// This will remove the child from hierachy and UI world.
494    fn remove_first(&mut self);
495
496    /// Remove the last child.
497    /// This will remove the child from hierachy and UI world.
498    fn remove_last(&mut self);
499}
500
501#[derive(Component)]
502pub(crate) struct MakaraWidget;
503
504/// Component used to store focus state of a widget.
505#[derive(Component)]
506pub struct WidgetFocus(pub bool);
507
508/// Style for all container box. Example, button, row, column ..
509#[derive(Bundle, Clone)]
510pub struct ContainerStyle {
511    pub node: Node,
512    pub background_color: BackgroundColor,
513    pub border_color: BorderColor,
514    pub shadow: BoxShadow,
515    pub z_index: ZIndex,
516}
517
518impl Default for ContainerStyle {
519    fn default() -> Self {
520        Self {
521            node: Node::default(),
522            background_color: BackgroundColor::default(),
523            border_color: BorderColor::default(),
524            z_index: ZIndex::default(),
525            shadow: BoxShadow::new(
526                Color::BLACK.with_alpha(0.8),
527                px(0.0),
528                px(1.0),
529                px(1.0),
530                px(2.0),
531            ),
532        }
533    }
534}
535
536/// Type of theme for makara application.
537#[derive(Default, PartialEq, Eq)]
538pub enum Theme {
539    #[default]
540    Light,
541    Dark
542}
543
544/// Resource used to track current theme.
545#[derive(Resource, Default)]
546pub struct MakaraTheme {
547    pub theme: Theme
548}
549
550impl MakaraTheme {
551    /// Change current theme to the provided one.
552    pub fn change_theme(&mut self, theme: Theme) {
553        self.theme = theme;
554    }
555}
556
557/// Resource used to store related data for cosmic-text.
558#[derive(Resource)]
559pub struct MakaraTextEditContext {
560    pub font_system: FontSystem,
561    pub swash_cache: SwashCache,
562    pub attrs: Attrs<'static>
563}
564
565impl Default for MakaraTextEditContext {
566    fn default() -> Self {
567        Self {
568            font_system: FontSystem::new(),
569            swash_cache: SwashCache::new(),
570            attrs: Attrs::new()
571        }
572    }
573}
574
575// mouse out observer for widget
576pub(crate) fn on_mouse_out(
577    mut out: On<Pointer<Out>>,
578    mut commands: Commands,
579    mut tooltips: Query<
580        (&mut Node, &ComputedNode, &TooltipPosition, &UseTooltip),
581        With<MakaraTooltip>
582    >,
583    widgets: Query<&Children, With<MakaraWidget>>,
584    window: Single<Entity, With<Window>>,
585) {
586    if let Ok(children) = widgets.get(out.entity) {
587        show_or_hide_tooltip(false, &mut tooltips, None, None, children);
588    }
589
590    commands.entity(*window).insert(CursorIcon::System(SystemCursorIcon::Default));
591    commands.trigger(MouseOut { entity: out.entity });
592    out.propagate(false);
593}