Widget

Trait Widget 

Source
pub trait Widget: Send + 'static {
    // Required methods
    fn update(pa: &mut Pass, handle: &Handle<Self>)
       where Self: Sized;
    fn needs_update(&self, pa: &Pass) -> bool;
    fn text(&self) -> &Text;
    fn text_mut(&mut self) -> &mut Text;

    // Provided methods
    fn on_focus(pa: &mut Pass, handle: &Handle<Self>)
       where Self: Sized { ... }
    fn on_unfocus(pa: &mut Pass, handle: &Handle<Self>)
       where Self: Sized { ... }
    fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent)
       where Self: Sized { ... }
    fn get_print_opts(&self) -> PrintOpts { ... }
    fn print(&self, pa: &Pass, painter: Painter, area: &RwArea) { ... }
}
Expand description

An area where Text will be printed to the screen

Most widgets are supposed to be passive widgets, that simply show information about the current state of Duat. In order to show that information, widgets make use of Text, which can show stylized text, buttons, and all sorts of other stuff. (For widgets that react to input, see the documentation forMode).

For a demonstration on how to create a widget, I will create a widget that shows the uptime, in seconds, for Duat.

use std::time::Duration;

use duat::{data::PeriodicChecker, prelude::*};

struct UpTime(Text, PeriodicChecker);

impl UpTime {
    fn new() -> Self {
        Self(
            Text::default(),
            PeriodicChecker::new(Duration::from_secs(1)),
        )
    }
}

In order to be a proper widget, it must have a Text to display. The PeriodicChecker will be explained later. Next, I implement Widget on UpTime:

use std::time::Duration;

use duat::{data::PeriodicChecker, prelude::*};

struct UpTime(Text, PeriodicChecker);

impl UpTime {
    fn new() -> Self {
        Self(
            Text::default(),
            PeriodicChecker::new(Duration::from_secs(1)),
        )
    }
}

impl Widget for UpTime {
    fn update(pa: &mut Pass, handle: &Handle<Self>) {
        todo!()
    }

    fn needs_update(&self, pa: &Pass) -> bool {
        todo!();
    }

    fn text(&self) -> &Text {
        &self.0
    }

    fn text_mut(&mut self) -> &mut Text {
        &mut self.0
    }
}

The Widget::update funcion is responsible for updating the Text of the Widget on every frame. However, it is only called if Widget::needs_update returns true. Note that this isn’t the only way to update Widgets, since in any place where you have global access throught the Pass (like hooks, commands, etc.), you can update any Handle for any Widget.

There are two other Widget functions: Widget::on_focus and Widget::on_unfocus, which are called when a Mode is set, and the Widget is focused or unfocused. For this example, since there are no Modes, these will not be used.

Next, I will finish implementing the Widget trait.

First of all, there needs to be a starting Instant to compare with the current moment in time. The correct moment to do that would be right as the setup function is called. This can be done safely with a OnceLock:

setup_duat!(setup);
use std::{sync::OnceLock, time::Instant};

use duat::prelude::*;
static START_TIME: OnceLock<Instant> = OnceLock::new();

fn setup() {
    START_TIME.set(Instant::now()).unwrap();
}

However, exposing that to end users is rather poor UX, so you should make use of Plugins instead:

use std::{sync::OnceLock, time::Instant};

use duat::prelude::*;
struct UpTimePlugin;
static START_TIME: OnceLock<Instant> = OnceLock::new();

impl Plugin for UpTimePlugin {
    fn plug(self, plugins: &Plugins) {
        START_TIME.set(Instant::now()).unwrap();
    }
}

Next, I’m going to implement the update function:

use duat::{data::PeriodicChecker, prelude::*};

static START_TIME: OnceLock<Instant> = OnceLock::new();

impl Widget for UpTime {
    // ...
    fn update(pa: &mut Pass, handle: &Handle<Self>) {
        let start = START_TIME.get().unwrap();
        let elapsed = start.elapsed();
        let mins = elapsed.as_secs() / 60;
        let secs = elapsed.as_secs() % 60;

        handle.write(pa).0 = txt!("[uptime.mins]{mins}m [uptime.secs]{secs}s");
    }
}

This should format the Text via txt! to show how many minutes and seconds have passed. However, I’m using the uptime.mins and updime.secs Forms, which aren’t set to anything, so they’ll just display normally colored text.

To solve that, just add more statements to the plugin:

use duat::prelude::*;

struct UpTimePlugin;

static START_TIME: OnceLock<Instant> = OnceLock::new();

impl Plugin for UpTimePlugin {
    fn plug(self, plugins: &Plugins) {
        START_TIME.set(Instant::now()).unwrap();
        form::set_weak("uptime", Form::green());
    }
}

Note the form::set_weak. This function “weakly” sets the Form, that is, it sets it only if it wasn’t set before via form::set. This is useful since the order in which the plugin is added and the Form is set by the end user doesn’t end up mattering.

Note also that I set uptime, rather than uptime.mins or uptime.secs. Due to Form inheritance, any form with a . in it will inherit from the parent, unless explicitly set to something else. this inheritance follows even when the parent changes. That is, if the user sets the uptime form to something else, uptime.mins and uptime.secs will also be set to that.

Now, I’m going to implement the needs_update function, that’s where the PeriodicChecker comes in to play:

use duat::{data::PeriodicChecker, prelude::*};

// This was set during the `setup` function
static START_TIME: OnceLock<Instant> = OnceLock::new();

struct UpTime(Text, PeriodicChecker);

impl Widget for UpTime {
    fn needs_update(&self, pa: &Pass) -> bool {
        // Returns `true` once per second
        self.1.check()
    }
}

The needs_update function is executed on every frame, however, it should only return true every second, which is when the update function will be called, updating the Widget.

Now, all that is left to do is placing the Widget on screen. To do that, I will make use of a hook to place them on the bottom of the Window, right below the Buffers:

use std::{
    sync::OnceLock,
    time::{Duration, Instant},
};

use duat::{data::PeriodicChecker, prelude::*};

static START_TIME: OnceLock<Instant> = OnceLock::new();

struct UpTimePlugin;

impl Plugin for UpTimePlugin {
    fn plug(self, plugins: &Plugins) {
        START_TIME.set(Instant::now()).unwrap();
        form::set_weak("uptime", Form::green());

        hook::add::<WindowCreated>(|pa, window| {
            let specs = ui::PushSpecs {
                side: ui::Side::Below,
                height: Some(1.0),
                ..Default::default()
            };
            window.push_inner(pa, UpTime::new(), specs);
            Ok(())
        });
    }
}

struct UpTime(Text, PeriodicChecker);

impl UpTime {
    fn new() -> Self {
        Self(
            Text::default(),
            PeriodicChecker::new(Duration::from_secs(1)),
        )
    }
}

impl Widget for UpTime {
    fn update(pa: &mut Pass, handle: &Handle<Self>) {
        let start = START_TIME.get().unwrap();
        let elapsed = start.elapsed();
        let mins = elapsed.as_secs() / 60;
        let secs = elapsed.as_secs() % 60;

        handle.write(pa).0 = txt!("[uptime.mins]{mins}m [uptime.secs]{secs}s");
    }

    fn needs_update(&self, pa: &Pass) -> bool {
        self.1.check()
    }

    fn text(&self) -> &Text {
        &self.0
    }

    fn text_mut(&mut self) -> &mut Text {
        &mut self.0
    }
}

Here, I’m adding a hook to push this widget to the bottom of the Window, right as said Window is opened. By using Window::push_inner, the Widget will be placed below the central Buffers region, but above other Widgets that were pushed to the bottom. If I wanted the Widget on the edges of the screen, I could use [Window::push_outer instead.

Required Methods§

Source

fn update(pa: &mut Pass, handle: &Handle<Self>)
where Self: Sized,

Updates the widget alongside its RwArea in the Handle

This function will be triggered when Duat deems that a change has happened to this Widget, which is when Widget::needs_update returns true.

It can also happen if RwData<Self>::has_changed or RwData::has_changed return true. This can happen from many places, like hooks, commands, editing by Modes, etc.

Importantly, update should be able to handle any number of changes that have taken place in this Widget, so you should avoid depending on state which may become desynchronized.

When implementing this, you are free to remove the where clause.

Source

fn needs_update(&self, pa: &Pass) -> bool

Tells Duat that this Widget should be updated

Determining wether a Widget should be updated, for a good chunk of them, will require some code like this:

use duat::prelude::*;

struct MyWidget(Handle<Buffer>);

impl Widget for MyWidget {
    // ...
    fn needs_update(&self, pa: &Pass) -> bool {
        self.0.has_changed(pa)
    }
}

In this case, MyWidget is telling Duat that it should be updated whenever the buffer in the Handle<Buffer> gets changed.

One interesting use case of this function is the StatusLine, which can be altered if any of its parts get changed, some of them depend on a Handle<Buffer>, but a lot of others depend on checking other data types in order to figure out if an update is needed.

Source

fn text(&self) -> &Text

The text that this widget prints out

Source

fn text_mut(&mut self) -> &mut Text

A mutable reference to the Text that is printed

Provided Methods§

Source

fn on_focus(pa: &mut Pass, handle: &Handle<Self>)
where Self: Sized,

Actions to do whenever this Widget is focused

When implementing this, you are free to remove the where clause.

Source

fn on_unfocus(pa: &mut Pass, handle: &Handle<Self>)
where Self: Sized,

Actions to do whenever this Widget is unfocused

When implementing this, you are free to remove the where clause.

Source

fn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent)
where Self: Sized,

How to handle a MouseEvent

Normally, nothing will be done, with the exception of button Tags which are triggered normally.

Source

fn get_print_opts(&self) -> PrintOpts

The configuration for how to print Text

The default configuration, used when print_opts is not implemented,can be found at PrintOpts::new.

Source

fn print(&self, pa: &Pass, painter: Painter, area: &RwArea)

Prints the widget

Very rarely shouuld you actually implement this method, one example of where this is actually implemented is in Buffer::print, where RwArea::print_with is called in order to simultaneously update the list of lines numbers, for widgets like LineNumbers to read.

Implementors§