diff --git a/CHANGELOG.md b/CHANGELOG.md index a67aa71..e002b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ All notable changes to eww will be listed here, starting at changes since versio - Add drag and drop functionality to eventbox - Add `search`, `captures`, `stringlength`, `arraylength` and `objectlength` functions for expressions (By: MartinJM, ElKowar) - Add `matches` function +- Add transform widget (By: druskus20) ### Notable Internal changes - Rework state management completely, now making local state and dynamic widget hierarchy changes possible. diff --git a/crates/eww/src/display_backend.rs b/crates/eww/src/display_backend.rs index bfb6d38..a279b57 100644 --- a/crates/eww/src/display_backend.rs +++ b/crates/eww/src/display_backend.rs @@ -69,8 +69,8 @@ mod platform { gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Top, top); gtk_layer_shell::set_anchor(&window, gtk_layer_shell::Edge::Bottom, bottom); - let xoffset = geometry.offset.x.relative_to(monitor.width); - let yoffset = geometry.offset.y.relative_to(monitor.height); + let xoffset = geometry.offset.x.pixels_relative_to(monitor.width); + let yoffset = geometry.offset.y.pixels_relative_to(monitor.height); if left { gtk_layer_shell::set_margin(&window, gtk_layer_shell::Edge::Left, xoffset); @@ -161,8 +161,8 @@ mod platform { let mon_end_y = (monitor_rect.y + monitor_rect.height) as u32 - 1u32; let dist = match strut_def.side { - Side::Left | Side::Right => strut_def.dist.relative_to(monitor_rect.width) as u32, - Side::Top | Side::Bottom => strut_def.dist.relative_to(monitor_rect.height) as u32, + Side::Left | Side::Right => strut_def.dist.pixels_relative_to(monitor_rect.width) as u32, + Side::Top | Side::Bottom => strut_def.dist.pixels_relative_to(monitor_rect.height) as u32, }; // don't question it,..... diff --git a/crates/eww/src/widgets/mod.rs b/crates/eww/src/widgets/mod.rs index a930332..5da0f78 100644 --- a/crates/eww/src/widgets/mod.rs +++ b/crates/eww/src/widgets/mod.rs @@ -4,6 +4,7 @@ pub mod build_widget; pub mod circular_progressbar; pub mod def_widget_macro; pub mod graph; +pub mod transform; pub mod widget_definitions; /// Run a command that was provided as an attribute. diff --git a/crates/eww/src/widgets/transform.rs b/crates/eww/src/widgets/transform.rs new file mode 100644 index 0000000..87c7b03 --- /dev/null +++ b/crates/eww/src/widgets/transform.rs @@ -0,0 +1,181 @@ +use anyhow::{anyhow, Result}; +use glib::{object_subclass, wrapper}; +use gtk::{prelude::*, subclass::prelude::*}; +use std::{cell::RefCell, str::FromStr}; +use yuck::value::NumWithUnit; + +use crate::error_handling_ctx; + +wrapper! { + pub struct Transform(ObjectSubclass) + @extends gtk::Bin, gtk::Container, gtk::Widget; +} + +pub struct TransformPriv { + rotate: RefCell, + translate_x: RefCell>, + translate_y: RefCell>, + scale_x: RefCell>, + scale_y: RefCell>, + content: RefCell>, +} + +// This should match the default values from the ParamSpecs +impl Default for TransformPriv { + fn default() -> Self { + TransformPriv { + rotate: RefCell::new(0.0), + translate_x: RefCell::new(None), + translate_y: RefCell::new(None), + scale_x: RefCell::new(None), + scale_y: RefCell::new(None), + content: RefCell::new(None), + } + } +} + +impl ObjectImpl for TransformPriv { + fn properties() -> &'static [glib::ParamSpec] { + use once_cell::sync::Lazy; + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + glib::ParamSpec::new_double( + "rotate", + "Rotate", + "The Rotation", + f64::MIN, + f64::MAX, + 0f64, + glib::ParamFlags::READWRITE, + ), + glib::ParamSpec::new_string("translate-x", "Translate x", "The X Translation", None, glib::ParamFlags::READWRITE), + glib::ParamSpec::new_string("translate-y", "Translate y", "The Y Translation", None, glib::ParamFlags::READWRITE), + glib::ParamSpec::new_string("scale-x", "Scale x", "The amount to scale in x", None, glib::ParamFlags::READWRITE), + glib::ParamSpec::new_string("scale-y", "Scale y", "The amount to scale in y", None, glib::ParamFlags::READWRITE), + ] + }); + + PROPERTIES.as_ref() + } + + fn set_property(&self, obj: &Self::Type, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + match pspec.name() { + "rotate" => { + self.rotate.replace(value.get().unwrap()); + obj.queue_draw(); // Queue a draw call with the updated value + } + "translate-x" => { + self.translate_x.replace(value.get().unwrap()); + obj.queue_draw(); // Queue a draw call with the updated value + } + "translate-y" => { + self.translate_y.replace(value.get().unwrap()); + obj.queue_draw(); // Queue a draw call with the updated value + } + "scale-x" => { + self.scale_x.replace(value.get().unwrap()); + obj.queue_draw(); // Queue a draw call with the updated value + } + "scale-y" => { + self.scale_y.replace(value.get().unwrap()); + obj.queue_draw(); // Queue a draw call with the updated value + } + x => panic!("Tried to set inexistant property of Transform: {}", x,), + } + } + + fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "rotate" => self.rotate.borrow().to_value(), + "translate_x" => self.translate_x.borrow().to_value(), + "translate_y" => self.translate_y.borrow().to_value(), + "scale_x" => self.scale_x.borrow().to_value(), + "scale_y" => self.scale_y.borrow().to_value(), + x => panic!("Tried to access inexistant property of Transform: {}", x,), + } + } +} + +#[object_subclass] +impl ObjectSubclass for TransformPriv { + type ParentType = gtk::Bin; + type Type = Transform; + + const NAME: &'static str = "Transform"; + + fn class_init(klass: &mut Self::Class) { + klass.set_css_name("transform"); + } +} + +impl Transform { + pub fn new() -> Self { + glib::Object::new::(&[]).expect("Failed to create Transform Widget") + } +} + +impl ContainerImpl for TransformPriv { + fn add(&self, container: &Self::Type, widget: >k::Widget) { + if let Some(content) = &*self.content.borrow() { + // TODO: Handle this error when populating children widgets instead + error_handling_ctx::print_error(anyhow!("Error, trying to add multiple children to a circular-progress widget")); + self.parent_remove(container, content); + } + self.parent_add(container, widget); + self.content.replace(Some(widget.clone())); + } +} + +impl BinImpl for TransformPriv {} +impl WidgetImpl for TransformPriv { + fn draw(&self, widget: &Self::Type, cr: &cairo::Context) -> Inhibit { + let res: Result<()> = try { + let rotate = *self.rotate.borrow(); + let total_width = widget.allocated_width() as f64; + let total_height = widget.allocated_height() as f64; + + cr.save()?; + + let translate_x = match &*self.translate_x.borrow() { + Some(tx) => NumWithUnit::from_str(&tx)?.pixels_relative_to(total_width as i32) as f64, + None => 0.0, + }; + + let translate_y = match &*self.translate_y.borrow() { + Some(ty) => NumWithUnit::from_str(&ty)?.pixels_relative_to(total_height as i32) as f64, + None => 0.0, + }; + + let scale_x = match &*self.scale_x.borrow() { + Some(sx) => NumWithUnit::from_str(&sx)?.perc_relative_to(total_width as i32) as f64 / 100.0, + None => 1.0, + }; + + let scale_y = match &*self.scale_y.borrow() { + Some(sy) => NumWithUnit::from_str(&sy)?.perc_relative_to(total_height as i32) as f64 / 100.0, + None => 1.0, + }; + + cr.scale(scale_x, scale_y); + cr.rotate(perc_to_rad(rotate)); + cr.translate(translate_x, translate_y); + + // Children widget + if let Some(child) = &*self.content.borrow() { + widget.propagate_draw(child, &cr); + } + + cr.restore()?; + }; + + if let Err(error) = res { + error_handling_ctx::print_error(error) + }; + + gtk::Inhibit(false) + } +} + +fn perc_to_rad(n: f64) -> f64 { + (n / 100f64) * 2f64 * std::f64::consts::PI +} diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index 27c60a0..54949e2 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -1,5 +1,5 @@ #![allow(clippy::option_map_unit_fn)] -use super::{build_widget::BuilderArgs, circular_progressbar::*, run_command}; +use super::{build_widget::BuilderArgs, circular_progressbar::*, transform::*, run_command}; use crate::{ def_widget, enum_parse, error::DiagError, @@ -60,6 +60,7 @@ pub(super) fn widget_use_to_gtk_widget(bargs: &mut BuilderArgs) -> Result build_gtk_event_box(bargs)?.upcast(), "circular-progress" => build_circular_progress_bar(bargs)?.upcast(), "graph" => build_graph(bargs)?.upcast(), + "transform" => build_transform(bargs)?.upcast(), "scale" => build_gtk_scale(bargs)?.upcast(), "progress" => build_gtk_progress(bargs)?.upcast(), "image" => build_gtk_image(bargs)?.upcast(), @@ -779,6 +780,26 @@ fn build_gtk_calendar(bargs: &mut BuilderArgs) -> Result { Ok(gtk_widget) } +/// @widget transform +/// @desc A widget that applies transformations to its content. They are applied in the following +/// order: rotate->translate->scale) +fn build_transform(bargs: &mut BuilderArgs) -> Result { + let w = Transform::new(); + def_widget!(bargs, _g, w, { + // @prop rotation - the percentage to rotate + prop(rotate: as_f64) { w.set_property("rotate", rotate)?; }, + // @prop translate-x - the amount to translate in the x direction (px or %) + prop(translate_x: as_string) { w.set_property("translate-x", translate_x)?; }, + // @prop translate-y - the amount to translate in the y direction (px or %) + prop(translate_y: as_string) { w.set_property("translate-y", translate_y)?; }, + // @prop scale_x - the amount to scale in the x direction (px or %) + prop(scale_x: as_string) { w.set_property("scale-x", scale_x)?; }, + // @prop scale_y - the amount to scale in the y direction (px or %) + prop(scale_y: as_string) { w.set_property("scale-y", scale_y)?; }, + }); + Ok(w) +} + /// @widget circular-progress /// @desc A widget that displays a circular progress bar fn build_circular_progress_bar(bargs: &mut BuilderArgs) -> Result { diff --git a/crates/yuck/src/value/coords.rs b/crates/yuck/src/value/coords.rs index cb59663..1c372bf 100644 --- a/crates/yuck/src/value/coords.rs +++ b/crates/yuck/src/value/coords.rs @@ -26,12 +26,19 @@ pub enum NumWithUnit { } impl NumWithUnit { - pub fn relative_to(&self, max: i32) -> i32 { + pub fn pixels_relative_to(&self, max: i32) -> i32 { match *self { NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32, NumWithUnit::Pixels(n) => n, } } + + pub fn perc_relative_to(&self, max: i32) -> i32 { + match *self { + NumWithUnit::Percent(n) => n, + NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as i32, + } + } } impl FromStr for NumWithUnit { @@ -86,7 +93,7 @@ impl Coords { /// resolve the possibly relative coordinates relative to a given containers size pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { - (self.x.relative_to(width), self.y.relative_to(height)) + (self.x.pixels_relative_to(width), self.y.pixels_relative_to(height)) } } diff --git a/out.gif b/out.gif deleted file mode 100644 index d71494a..0000000 Binary files a/out.gif and /dev/null differ