-
Notifications
You must be signed in to change notification settings - Fork 27
Making Your Own Blocks
Making your own blocks is designed to be straightforward but flexible. I'm hoping to merge most community blocks, so if you do something cool, make a pull request!
Before developing, we should fork the repository so we can make commits and then eventually do a pull request later if we want to.
Just hit the fork button at the top right, and then clone the forked repo.
$ git clone https://github.com/<user>/wired-notify.git
$ cd ./wired_notify
And let's try a build to make sure everything's ok. If this fails, either your rust install is outdated or configured, you're missing dependencies, or I've screwed something up.
$ cargo build
First of all, let's create the file for our block. For the purposes of this tutorial, I'm going to make a simplified TextBlock
.
$ ls
Cargo.lock Cargo.toml LICENSE README.md readme_stuff src target wired_derive wired.ron
$ touch ./src/rendering/blocks/text_block_demo.rs # Blocks go in /src/rendering/blocks/
Each block should have a Properties
struct which defines both config parameters and internal state. This struct must implement Debug
, Deserialize
, and Clone
.
// /src/rendering/blocks/notification_block.rs
use serde::Deserialize;
use crate::config::{Padding, Color};
// A custom struct for these parameters will make things clearer in configuration.
#[derive(Debug, Deserialize, Clone)]
pub struct WidthHeight {
width: i32,
height: i32,
}
#[derive(Debug, Deserialize, Clone)]
pub struct TextBlockDemoParameters {
pub padding: Padding,
pub text: String,
pub font: String,
pub dimensions: WidthHeight,
pub color: Color,
// Cached property example.
#[serde(skip)]
real_text: String,
}
To add Wired behavior to the block, we need to implement the DrawableLayoutElement
trait, which has 2 required methods, and 3 optional methods:
use crate::maths_utility::{Vec2, Rect};
use crate::rendering::layout::{DrawableLayoutElement, Hook};
impl DrawableLayoutElement for TextBlockDemoParameters {
// Required
fn predict_rect_and_init(&mut self, hook: &Hook, offset: &Vec2, parent_rect: &Rect, window: &NotifyWindow) -> Rect {
}
fn draw(&self, hook: &Hook, offset: &Vec2, parent_rect: &Rect, window: &NotifyWindow) -> Rect {
}
// Optional
fn update(&mut self, delta_time: Duration, window: &NotifyWindow) -> bool {
}
fn clicked(&mut self, _window: &NotifyWindow) -> bool {
}
fn hovered(&mut self, _entered: bool, _window: &NotifyWindow) -> bool {
}
}
The first 2 methods, predict_rect_and_init()
and draw()
are very similar, and are mainly split to enable some optimizations and sequencing. Both of these must be implemented. predict_rect_and_init()
is called once at window creation. It is responsible for predicting the size and position of the block so that the window size can be determined, but can also be used to cache some operations; ImageBlock
uses this to resize the image once and then use that for successive draws, for instance.
use crate::maths_utility::{Vec2, Rect};
use crate::rendering::layout::{DrawableLayoutElement, LayoutBlock, Hook};
use crate::rendering::text::EllipsizeMode;
use crate::config::Config;
impl DrawableLayoutElement for TextBlockDemoParameters {
fn predict_rect_and_init(&mut self, hook: &Hook, offset: &Vec2, parent_rect: &Rect, window: &NotifyWindow) -> Rect {
// Replace our text in the config with notification text.
// This has a bug, so don't use it in production.
// See https://github.com/Toqozz/wired-notify/blob/master/src/rendering/blocks/text_block.rs for details.
let text = self.text.clone().replace("%s", &window.notification.summary).replace("%b", &window.notification.body);
// Every window has a text render component attached to help with some text operations: https://github.com/Toqozz/wired-notify/blob/master/src/rendering/text.rs
window.text.set_text(&text, &self.font, self.dimensions.width, self.dimensions.height, &EllipsizeMode::default());
// Get the rect surrounding this text, plus padding.
let mut rect = window.text.get_sized_padded_rect(&self.padding, &self.dimensions.width, &self.dimensions.height);
// Cache the text, so we don't have to do string replacement every iteration.
self.real_text = text;
// Lastly we need to position our rect correctly. To do this, just feed `find_anchor_pos()` the provided parameters and
// your new rect, and it will find the appropriate x, y coordinates.
let pos = LayoutBlock::find_anchor_pos(hook, offset, parent_rect, &rect);
rect.set_xy(pos.x, pos.y);
rect
}
fn draw(&self, _hook: &Hook, _offset: &Vec2, parent_rect: &Rect, window: &NotifyWindow) -> Rect {
// For drawing, wired uses rust bindings for the Cairo 2D graphics library.
// Check out the documentation (https://www.cairographics.org/documentation/) and the rust bindings (https://docs.rs/cairo-rs/0.9.1/cairo/struct.Context.html) to learn more.
// All drawing from this point should draw over the background.
window.context.set_operator(cairo::Operator::Over);
window.text.set_text(&self.real_text, &self.font, &self.dimensions.width, &self.dimensions.height as i32, &EllipsizeMode::default());
// Since our rect doesn't update or move, it would be more efficient to cache this rect and just use that, but we're lazy.
let mut rect = window.text.get_sized_padded_rect(&self.padding, &self.dimensions.width, &self.dimensions.height);
let mut pos = LayoutBlock::find_anchor_pos(hook, offset, parent_rect, &rect);
// Text is rendered starting from the top left hand corner of the provided position. Since our rect has padding applied, we need
// to add that padding to actually draw with padding.
// `paint_padded()` does this work for you.
window.text.paint_padded(&window.context, &pos, &self.color, &self.padding);
rect.set_xy(pos.x, pos.y);
rect
}
}
To be able to use our block, we first have to tell Wired about it. This is pretty straightforward, and should hopefully be automated in the future.
Firstly, in src/rendering/blocks/mod.rs
, add we add exports for our new block so our other source can access it:
pub mod notification_block;
pub mod text_block;
pub mod scrolling_text_block;
pub mod image_block;
pub mod button_block;
pub mod text_block_demo; // same as the name of our file: text_block_demo.rs
pub use notification_block::*;
pub use text_block::*;
pub use scrolling_text_block::*;
pub use image_block::*;
pub use button_block::*;
pub use text_block_demo::*; // as above
Then, in layout.rs
, we find the LayoutElement
enum and add a new entry:
pub enum LayoutElement {
NotificationBlock(NotificationBlockParameters),
TextBlock(TextBlockParameters),
ScrollingTextBlock(ScrollingTextBlockParameters),
ImageBlock(ImageBlockParameters),
TextBlockDemo(TextBlockDemoParameters),
}
Lastly, compile and run so we can use our new block:
$ cargo run
Compiling wired v0.8.0 (/home/toqoz/code/rust/wired)
Finished dev [unoptimized + debuginfo] target(s) in 4.51s
Running `target/debug/wired`
In queue for notification bus name -- is another notification daemon running?
If you get In queue for notification bus name -- is another notification daemon running?
, you need to close the existing notification daemon:
In queue for notification bus name -- is another notification daemon running?
^C
$ pkill wired
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
Running `target/debug/wired`
Acquired notification bus name.
DBus Init Success.
Now you can use your new block just like any other!
// ~/.config/wired/wired.ron
...
layout_blocks: [
(
name: "root",
parent: "",
hook: Hook(parent_anchor: TL, self_anchor: TL),
offset: Vec2(x: 7.0, y: 7.0),
params: NotificationBlock((
...
)),
),
(
name: "summary",
parent: "root",
hook: Hook(parent_anchor: TL, self_anchor: TL),
offset: Vec2(x: 0.0, y: 0.0),
params: TextBlockDemo((
text: "%s",
font: "Arial Bold 10",
color: Color(hex: "#ebdbb2"),
padding: Padding(left: 20.0, right: 20.0, top: 20.0, bottom: 20.0),
dimensions: (width: 150.0, height: 50.0), // struct names are optionally omitted in config.
)),
),
...
],
...
We're done!
$ notify-send "Just the summary."
I'm looking to merge most block pull requests; if you make something cool, share it! You might be asked to create a documentation page similar to the ones seen in the wiki.