Skip to main content

duat_base/widgets/
info.rs

1//! The simplest widget, just shows [`Text`]
2//!
3//! This is a very simple `Widget`, it basically just exists so I
4//! don't have to define a new `Widget` every time I want to show
5//! static information.
6use std::sync::Once;
7
8use duat_core::{
9    context::Handle,
10    data::Pass,
11    hook::{self, OnMouseEvent, WidgetOpened},
12    mode::MouseEventKind,
13    opts::PrintOpts,
14    text::{Text, TextMut},
15    ui::Widget,
16};
17
18/// Adds the hooks for the [`Info`] widget.
19pub fn add_info_hooks() {
20    use MouseEventKind::{ScrollDown, ScrollUp};
21
22    hook::add::<OnMouseEvent<Info>>(|pa, event| match event.kind {
23        ScrollDown | ScrollUp => {
24            let (info, area) = event.handle.write_with_area(pa);
25            let scroll = if let ScrollDown = event.kind { 3 } else { -3 };
26            area.scroll_ver(&info.text, scroll, info.print_opts());
27        }
28        _ => {}
29    });
30
31    hook::add::<WidgetOpened<Info>>(|pa, info| {
32        let (info, area) = info.write_with_area(pa);
33        let size = area.size_of_text(info.print_opts(), &info.text).unwrap();
34        _ = area.set_width(size.x);
35        _ = area.set_height(size.y);
36    });
37}
38
39/// A simple static widget, meant to just convey information
40///
41/// This is the most flexible of widgets, you can use it anywhere,
42/// just by pushing it around, or spawning it however you want, by
43/// making use of the [`PushSpecs`], [`DynSpawnSpecs`] o
44/// [`StaticSpawnSpecs`].
45///
46/// [`PushSpecs`]: duat_core::ui::PushSpecs
47/// [`DynSpawnSpecs`]: duat_core::ui::DynSpawnSpecs
48/// [`StaticSpawnSpecs`]: duat_core::ui::StaticSpawnSpecs
49pub struct Info {
50    /// The [`Text`] that will be shown by this widget
51    text: Text,
52}
53
54impl Info {
55    /// Returns a new `Info` widget
56    ///
57    /// This is the only [`Widget`] in `duat-base` that can be
58    /// acquired this way, since it's supposed to be versatile in
59    /// where you position it. Every other widget has to be placed in
60    /// specific locations, so they don't offer a `new` method, which
61    /// could be used in order to place them willy nilly.
62    pub fn new(text: Text) -> Self {
63        static ONCE: Once = Once::new();
64        ONCE.call_once(|| {});
65
66        Self { text }
67    }
68
69    /// Mutate the [`Text`] of this `Info`.
70    ///
71    /// This will also resize the widget to fit as much of it as
72    /// possible.
73    pub fn set_text(pa: &mut Pass, info: &Handle<Self>, func: impl FnOnce(&mut Text)) {
74        let (info, area) = info.write_with_area(pa);
75        func(&mut info.text);
76
77        let size = area.size_of_text(info.print_opts(), &info.text).unwrap();
78        _ = area.set_width(size.x);
79        _ = area.set_height(size.y);
80    }
81}
82
83impl Widget for Info {
84    fn text(&self) -> &Text {
85        &self.text
86    }
87
88    fn text_mut(&mut self) -> TextMut<'_> {
89        self.text.as_mut()
90    }
91
92    fn print_opts(&self) -> PrintOpts {
93        let mut opts = PrintOpts::new();
94        opts.wrap_lines = true;
95        opts.tabstop = 2;
96        opts
97    }
98}