Skip to main content

ferroui_controls/
hyperlink_button.rs

1use crate::metadata::PseudoClassesAttribute;
2use crate::primitives::TemplatedControlImpl;
3use crate::{Button, ButtonImpl, ButtonImplExt, ContentControlImpl, ControlImpl, TopLevel};
4use ferroui_base::input::InputElementImpl;
5use ferroui_base::interactivity::InteractiveImpl;
6use ferroui_base::layout::LayoutableImpl;
7use ferroui_base::threading::Dispatcher;
8use ferroui_base::utilities::Uri;
9use ferroui_base::{
10    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt,
11    FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty, VisualImpl,
12};
13
14// See: https://www.w3schools.com/cssref/sel_visited.php
15const PC_VISITED: &str = ":visited";
16
17/// A button control that functions as a navigateable hyperlink.
18#[repr(C)]
19pub struct HyperlinkButton {
20    base: Button,
21}
22
23ferro_class!(HyperlinkButton: Button);
24ferroui_base::ferro_class_info!(HyperlinkButton { new: HyperlinkButton::new });
25ferro_impl_classes!(
26    HyperlinkButton: StyledElementImpl,
27    VisualImpl,
28    LayoutableImpl,
29    InteractiveImpl,
30    InputElementImpl,
31    ControlImpl,
32    TemplatedControlImpl,
33    ContentControlImpl
34);
35
36impl ButtonImpl for HyperlinkButton {
37    fn on_click(this: &Self) {
38        Self::parent_on_click(this);
39
40        let uri = this.navigate_uri();
41        if let Some(uri) = uri {
42            let this = this.to_ref();
43            // The task is not observed, as the posted asynchronous action
44            // of the reference.
45            let _ = Dispatcher::ui_thread().invoke_async_task_local(move || async move {
46                let Some(top_level) = TopLevel::get_top_level(Some(&this)) else {
47                    return;
48                };
49                let success = top_level.launcher().launch_uri_async(&uri).await;
50                if success {
51                    this.set_current_value(Self::is_visited_property(), true);
52                }
53            });
54        }
55    }
56}
57
58impl FerroObjectImpl for HyperlinkButton {
59    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
60        Self::parent_on_property_changed(this, change);
61
62        if change.property() == Self::is_visited_property().as_property() {
63            this.pseudo_classes().set(PC_VISITED, change.get_new_value::<bool>());
64        }
65    }
66}
67
68impl HyperlinkButton {
69    /// The pseudoclasses set by the class.
70    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_VISITED]);
71}
72
73ferroui_base::ferro_properties! { impl HyperlinkButton {
74    ferro_property!(
75        /// Defines the `IsVisited` property.
76        pub fn is_visited_property() -> StyledProperty<bool> {
77            FerroProperty::register::<HyperlinkButton, _>("IsVisited", false)
78        }
79    );
80
81    ferro_property!(
82        /// Defines the `NavigateUri` property.
83        pub fn navigate_uri_property() -> StyledProperty<Option<Uri>> {
84            FerroProperty::register::<HyperlinkButton, _>("NavigateUri", None)
85        }
86    );
87} }
88
89impl HyperlinkButton {
90    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
91    pub fn construct() -> Self {
92        Self { base: Button::construct() }
93    }
94
95    /// Creates a hyperlink button.
96    pub fn new() -> Ref<Self> {
97        instantiate(Self::construct())
98    }
99
100    /// A value indicating whether the `NavigateUri` has been visited.
101    pub fn is_visited(&self) -> bool {
102        self.get_value(Self::is_visited_property())
103    }
104
105    pub fn set_is_visited(&self, value: bool) {
106        self.set_value(Self::is_visited_property(), value)
107    }
108
109    /// The Uniform Resource Identifier (URI) automatically navigated to
110    /// when the hyperlink button is clicked.
111    ///
112    /// The URI may be any website or file location that can be launched
113    /// using the launcher service.
114    ///
115    /// If a URI should not be automatically launched, leave this property
116    /// unset and use the `Click` event and `is_visited` directly.
117    pub fn navigate_uri(&self) -> Option<Uri> {
118        self.get_value(Self::navigate_uri_property())
119    }
120
121    pub fn set_navigate_uri(&self, value: Option<Uri>) {
122        self.set_value(Self::navigate_uri_property(), value)
123    }
124}