Skip to main content

vizia_core/views/
frame.rs

1use crate::prelude::*;
2
3/// The position of the title on the frame border.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum FrameTitlePosition {
6    /// Title at the top-left corner
7    #[default]
8    TopLeft,
9    /// Title at the top-center
10    TopCenter,
11    /// Title at the top-right corner
12    TopRight,
13}
14
15/// A container widget that groups related content with a border and optional title.
16///
17/// The frame displays a border around its content and supports an optional title
18/// that is positioned to intersect the frame's border, similar to an HTML fieldset.
19///
20/// # Examples
21///
22/// ```no_run
23/// # use vizia_core::prelude::*;
24/// # let cx = &mut Context::default();
25/// Frame::new(cx, |cx| {
26///     Label::new(cx, "Frame content").hoverable(false);
27/// }).title_position(FrameTitlePosition::TopCenter);
28/// ```
29pub struct Frame {
30    title_position: Signal<FrameTitlePosition>,
31}
32
33impl Frame {
34    /// Creates a new frame with content but no title.
35    pub fn new(cx: &mut Context, content: impl FnOnce(&mut Context)) -> Handle<Self> {
36        let title_position = Signal::new(FrameTitlePosition::default());
37
38        Self { title_position }.build(cx, |cx| {
39            // Content (top layer)
40            content(cx);
41        })
42    }
43
44    /// Creates a new frame with a title and content.
45    ///
46    /// The title is positioned to intersect the frame's border.
47    pub fn with_title<S: View>(
48        cx: &mut Context,
49        title: impl FnOnce(&mut Context) -> Handle<S>,
50        content: impl FnOnce(&mut Context),
51    ) -> Handle<Self> {
52        let title_position = Signal::new(FrameTitlePosition::default());
53
54        Self { title_position }.build(cx, |cx| {
55            // Title - positioned absolutely with negative top offset
56
57            title(cx).class("frame-title").bind(title_position, move |handle| {
58                let pos = title_position.get();
59                match pos {
60                    FrameTitlePosition::TopLeft => {
61                        handle
62                            .toggle_class("left", true)
63                            .toggle_class("center", false)
64                            .toggle_class("right", false);
65                    }
66                    FrameTitlePosition::TopCenter => {
67                        handle
68                            .toggle_class("left", false)
69                            .toggle_class("center", true)
70                            .toggle_class("right", false);
71                    }
72                    FrameTitlePosition::TopRight => {
73                        handle
74                            .toggle_class("left", false)
75                            .toggle_class("center", false)
76                            .toggle_class("right", true);
77                    }
78                }
79            });
80
81            // Content
82            content(cx);
83        })
84    }
85}
86
87impl View for Frame {
88    fn element(&self) -> Option<&'static str> {
89        Some("frame")
90    }
91}
92
93impl Handle<'_, Frame> {
94    /// Set the position of the title on the frame border.
95    pub fn title_position(self, position: FrameTitlePosition) -> Self {
96        self.modify(|frame| frame.title_position.set(position))
97    }
98}