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§
Sourcefn update(pa: &mut Pass, handle: &Handle<Self>)where
Self: Sized,
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.
Sourcefn needs_update(&self, pa: &Pass) -> bool
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.
Provided Methods§
Sourcefn on_focus(pa: &mut Pass, handle: &Handle<Self>)where
Self: Sized,
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.
Sourcefn on_unfocus(pa: &mut Pass, handle: &Handle<Self>)where
Self: Sized,
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.
Sourcefn on_mouse_event(pa: &mut Pass, handle: &Handle<Self>, event: MouseEvent)where
Self: Sized,
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.
Sourcefn get_print_opts(&self) -> PrintOpts
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.
Sourcefn print(&self, pa: &Pass, painter: Painter, area: &RwArea)
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.