Advanced window positioning with anchors (#55)
* Implement anchoring * adjust documentation
This commit is contained in:
parent
0f68a76507
commit
9146905314
10 changed files with 269 additions and 136 deletions
|
@ -5,7 +5,10 @@ sort_by = "weight"
|
|||
+++
|
||||
|
||||
|
||||
Eww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust) Is a widgeting system made in [rust](https://www.rust-lang.org/), which let's you create your own widgets similarly to how you can in AwesomeWM. The key difference: It is independent of your window manager!
|
||||
Eww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust)
|
||||
is a widgeting system made in [rust](https://www.rust-lang.org/),
|
||||
which let's you create your own widgets similarly to how you can in AwesomeWM.
|
||||
The key difference: It is independent of your window manager!
|
||||
|
||||
Configured in XML and themed using CSS, it is easy to customize and provides all the flexibility you need!
|
||||
|
||||
|
@ -17,7 +20,9 @@ Configured in XML and themed using CSS, it is easy to customize and provides all
|
|||
* rustc
|
||||
* cargo (nightly toolchain)
|
||||
|
||||
Rather than with your system package manager, I recommend installing it using [rustup](https://rustup.rs/), as this makes it easy to use the nightly toolchain, which is necessary to build eww.
|
||||
Rather than with your system package manager,
|
||||
I recommend installing it using [rustup](https://rustup.rs/),
|
||||
as this makes it easy to use the nightly toolchain necessary to build eww.
|
||||
|
||||
### Building
|
||||
|
||||
|
|
|
@ -188,77 +188,23 @@ To use that it would look like this:
|
|||
```
|
||||
### The `<windows>` block {#windows-block}
|
||||
|
||||
This is the part the Eww reads and loads. The `<windows>` config should look something like this:
|
||||
All different windows you might want to use are defined in the `<windows>` block.
|
||||
The `<windows>` config should look something like this:
|
||||
|
||||
```xml
|
||||
<windows>
|
||||
<window name="main_window" stacking="fg">
|
||||
<size x="300" y="300" />
|
||||
<pos x="0" y="500" />
|
||||
<geometry anchor="top left" x="300px" y="50%" width="25%" height="20px">
|
||||
<widget>
|
||||
<main/>
|
||||
</widget>
|
||||
</window>
|
||||
</windows>
|
||||
```
|
||||
`<window name="main_window">` is the part that eww runs when you start it. In this example you would run eww by doing:
|
||||
```bash
|
||||
./eww open main_window
|
||||
```
|
||||
but if renamed the `<window>` to be `<window name="apple">` we would run eww by doing:
|
||||
```bash
|
||||
./eww open apple
|
||||
```
|
||||
|
||||
The `stacking="fg"` says where the widget will be stacked. Possible values here are `foreground`, `fg`, `background` and `bg`.
|
||||
`foreground` or `fg` *always* stays above windows.
|
||||
`background` or `bg` *always* stays behind windows. So it will stay on your desktop.
|
||||
The window block contains multiple elements to configure the window.
|
||||
- `<geometry>` is used to specify the position and size of the window.
|
||||
- `<widget>` will contain the widget that is shown in the window.
|
||||
|
||||
If you were to remove the `stacking="fg"` it would default it to `fg`.
|
||||
|
||||
You can also have multiple windows in one document by doing:
|
||||
|
||||
```xml
|
||||
<windows>
|
||||
<window name="main_window">
|
||||
<size x="300" y="300" />
|
||||
<pos x="0" y="500" />
|
||||
<widget>
|
||||
<main/>
|
||||
</widget>
|
||||
</window>
|
||||
<window name="main_window2">
|
||||
<size x="400" y="600"/>
|
||||
<pos x="0" y="0"/>
|
||||
<widget>
|
||||
<main2/>
|
||||
</widget>
|
||||
</window>
|
||||
</windows>
|
||||
```
|
||||
---
|
||||
|
||||
- `<size>` sets x-y size of the widget.
|
||||
- `<pos>` sets x-y position of the widget.
|
||||
- `<widget>` is the part which you say which `<def>` eww should run. So if we take the example config from before:
|
||||
```xml
|
||||
<definitions>
|
||||
<def name="clock">
|
||||
<box>
|
||||
The time is: {{my_time}} currently.
|
||||
</box>
|
||||
</def>
|
||||
<def name="main">
|
||||
<box>
|
||||
<clock my_time="{{date}}"/>
|
||||
</box>
|
||||
</def>
|
||||
</definitions>
|
||||
```
|
||||
and then look at
|
||||
```xml
|
||||
<widget>
|
||||
<main/>
|
||||
</widget>
|
||||
```
|
||||
we will see that eww will run `<def name="main">` and not `<def name="clock">`.
|
||||
The `stacking="fg"` specifies if the window should appear on top of or behind other windows.
|
||||
Possible values here are `foreground`, `fg`, `background` and `bg`. It defaults to `fg`.
|
||||
|
|
63
src/app.rs
63
src/app.rs
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
config,
|
||||
config::{window_definition::WindowName, WindowStacking},
|
||||
config::{window_definition::WindowName, AnchorPoint, WindowStacking},
|
||||
eww_state,
|
||||
script_var_handler::*,
|
||||
util,
|
||||
|
@ -25,6 +25,7 @@ pub enum EwwCommand {
|
|||
window_name: WindowName,
|
||||
pos: Option<Coords>,
|
||||
size: Option<Coords>,
|
||||
anchor: Option<AnchorPoint>,
|
||||
},
|
||||
CloseWindow {
|
||||
window_name: WindowName,
|
||||
|
@ -75,8 +76,13 @@ impl App {
|
|||
script_var_process::on_application_death();
|
||||
std::process::exit(0);
|
||||
}
|
||||
EwwCommand::OpenWindow { window_name, pos, size } => {
|
||||
self.open_window(&window_name, pos, size)?;
|
||||
EwwCommand::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
} => {
|
||||
self.open_window(&window_name, pos, size, anchor)?;
|
||||
}
|
||||
EwwCommand::CloseWindow { window_name } => {
|
||||
self.close_window(&window_name)?;
|
||||
|
@ -115,7 +121,13 @@ impl App {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn open_window(&mut self, window_name: &WindowName, pos: Option<Coords>, size: Option<Coords>) -> Result<()> {
|
||||
fn open_window(
|
||||
&mut self,
|
||||
window_name: &WindowName,
|
||||
pos: Option<Coords>,
|
||||
size: Option<Coords>,
|
||||
anchor: Option<config::AnchorPoint>,
|
||||
) -> Result<()> {
|
||||
// remove and close existing window with the same name
|
||||
let _ = self.close_window(window_name);
|
||||
|
||||
|
@ -135,10 +147,11 @@ impl App {
|
|||
|
||||
let monitor_geometry = display.get_default_screen().get_monitor_geometry(*screen_number);
|
||||
|
||||
window_def.position = pos.unwrap_or(window_def.position);
|
||||
window_def.size = size.unwrap_or(window_def.size);
|
||||
window_def.geometry.offset = pos.unwrap_or(window_def.geometry.offset);
|
||||
window_def.geometry.size = size.unwrap_or(window_def.geometry.size);
|
||||
window_def.geometry.anchor_point = anchor.unwrap_or(window_def.geometry.anchor_point);
|
||||
|
||||
let actual_window_rect = get_window_rectangle_in_screen(monitor_geometry, window_def.position, window_def.size);
|
||||
let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry);
|
||||
|
||||
let window = if window_def.focusable {
|
||||
gtk::Window::new(gtk::WindowType::Toplevel)
|
||||
|
@ -172,6 +185,16 @@ impl App {
|
|||
root_widget.get_style_context().add_class(&window_name.to_string());
|
||||
window.add(root_widget);
|
||||
|
||||
// Handle the fact that the gtk window will have a different size than specified,
|
||||
// as it is sized according to how much space it's contents require.
|
||||
// This is necessary to handle different anchors correctly in case the size was wrong.
|
||||
let (gtk_window_width, gtk_window_height) = window.get_size();
|
||||
window_def.geometry.size = Coords {
|
||||
x: NumWithUnit::Pixels(gtk_window_width),
|
||||
y: NumWithUnit::Pixels(gtk_window_height),
|
||||
};
|
||||
let actual_window_rect = window_def.geometry.get_window_rectangle(monitor_geometry);
|
||||
|
||||
window.show_all();
|
||||
|
||||
let gdk_window = window.get_window().context("couldn't get gdk window from gtk window")?;
|
||||
|
@ -210,10 +233,8 @@ impl App {
|
|||
|
||||
let windows = self.windows.clone();
|
||||
for (window_name, window) in windows {
|
||||
let old_pos = window.definition.position;
|
||||
let old_size = window.definition.size;
|
||||
window.gtk_window.close();
|
||||
self.open_window(&window_name, Some(old_pos.into()), Some(old_size.into()))?;
|
||||
self.open_window(&window_name, None, None, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -233,25 +254,3 @@ fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) {
|
|||
});
|
||||
window.set_visual(visual.as_ref());
|
||||
}
|
||||
|
||||
/// Calculate the window rectangle given the configured window [`pos`] and [`size`], which might be relative to the screen size.
|
||||
fn get_window_rectangle_in_screen(screen_rect: gdk::Rectangle, pos: Coords, size: Coords) -> gdk::Rectangle {
|
||||
gdk::Rectangle {
|
||||
x: match pos.x {
|
||||
NumWithUnit::Percent(n) => ((screen_rect.width as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => screen_rect.x + n,
|
||||
},
|
||||
y: match pos.y {
|
||||
NumWithUnit::Percent(n) => ((screen_rect.height as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => screen_rect.y + n,
|
||||
},
|
||||
width: match size.x {
|
||||
NumWithUnit::Percent(n) => ((screen_rect.width as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => n,
|
||||
},
|
||||
height: match size.y {
|
||||
NumWithUnit::Percent(n) => ((screen_rect.height as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => n,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,9 @@ impl WidgetDefinition {
|
|||
);
|
||||
}
|
||||
|
||||
let size: Option<_> = Option::zip(xml.attr("width").ok(), xml.attr("height").ok());
|
||||
let size: Option<Result<_>> = size.map(|(x, y)| Ok((x.parse()?, y.parse()?)));
|
||||
|
||||
WidgetDefinition {
|
||||
name: xml.attr("name")?.to_owned(),
|
||||
size: size.transpose()?,
|
||||
size: Option::zip(xml.parse_optional_attr("width")?, xml.parse_optional_attr("height")?),
|
||||
structure: WidgetUse::from_xml_node(xml.only_child()?)?,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,12 @@ pub mod element;
|
|||
pub mod eww_config;
|
||||
pub mod script_var;
|
||||
pub mod window_definition;
|
||||
pub mod window_geometry;
|
||||
pub mod xml_ext;
|
||||
pub use eww_config::*;
|
||||
pub use script_var::*;
|
||||
pub use window_definition::*;
|
||||
pub use window_geometry::*;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_xml_tag_is {
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
use crate::{ensure_xml_tag_is, value::Coords};
|
||||
use crate::ensure_xml_tag_is;
|
||||
use anyhow::*;
|
||||
use derive_more::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
|
||||
use super::*;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EwwWindowDefinition {
|
||||
pub position: Coords,
|
||||
pub size: Coords,
|
||||
pub geometry: EwwWindowGeometry,
|
||||
pub stacking: WindowStacking,
|
||||
pub screen_number: Option<i32>,
|
||||
pub widget: WidgetUse,
|
||||
|
@ -21,22 +19,18 @@ pub struct EwwWindowDefinition {
|
|||
impl EwwWindowDefinition {
|
||||
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
|
||||
ensure_xml_tag_is!(xml, "window");
|
||||
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
|
||||
let screen_number = xml.parse_optional_attr("screen")?;
|
||||
let focusable = xml.parse_optional_attr("focusable")?;
|
||||
|
||||
let size_node = xml.child("size")?;
|
||||
let size = Coords::from_strs(size_node.attr("x")?, size_node.attr("y")?)?;
|
||||
let pos_node = xml.child("pos")?;
|
||||
let position = Coords::from_strs(pos_node.attr("x")?, pos_node.attr("y")?)?;
|
||||
|
||||
let stacking = xml.attr("stacking").ok().map(|x| x.parse()).transpose()?.unwrap_or_default();
|
||||
let screen_number = xml.attr("screen").ok().map(|x| x.parse()).transpose()?;
|
||||
let focusable = xml.attr("focusable").ok().map(|x| x.parse()).transpose()?;
|
||||
let struts = xml.child("struts").ok().map(Struts::from_xml_element).transpose()?;
|
||||
|
||||
let widget = WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?;
|
||||
Ok(EwwWindowDefinition {
|
||||
position,
|
||||
size,
|
||||
widget,
|
||||
geometry: match xml.child("geometry") {
|
||||
Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
|
||||
Err(_) => EwwWindowGeometry::default(),
|
||||
},
|
||||
widget: WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?,
|
||||
stacking,
|
||||
screen_number,
|
||||
focusable: focusable.unwrap_or(false),
|
||||
|
@ -89,7 +83,8 @@ impl std::str::FromStr for WindowStacking {
|
|||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Hash, PartialEq, Eq, AsRef, FromStr, Display, Serialize, Deserialize, Default, From)]
|
||||
#[derive(Clone, Hash, PartialEq, Eq, AsRef, FromStr, Display, Serialize, Deserialize, Default, From, DebugCustom)]
|
||||
#[debug(fmt = "WindowName(\".0\")")]
|
||||
pub struct WindowName(String);
|
||||
|
||||
impl std::borrow::Borrow<str> for WindowName {
|
||||
|
@ -97,9 +92,3 @@ impl std::borrow::Borrow<str> for WindowName {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WindowName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "WindowName(\"{}\")", self.0)
|
||||
}
|
||||
}
|
||||
|
|
151
src/config/window_geometry.rs
Normal file
151
src/config/window_geometry.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use crate::value::Coords;
|
||||
use anyhow::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use super::xml_ext::XmlElement;
|
||||
|
||||
#[derive(Debug, derive_more::Display, Clone, Copy, Eq, PartialEq, SmartDefault, Serialize, Deserialize)]
|
||||
pub enum AnchorAlignment {
|
||||
#[display("start")]
|
||||
#[default]
|
||||
START,
|
||||
#[display("center")]
|
||||
CENTER,
|
||||
#[display("end")]
|
||||
END,
|
||||
}
|
||||
|
||||
impl AnchorAlignment {
|
||||
pub fn from_x_alignment(s: &str) -> Result<AnchorAlignment> {
|
||||
match s {
|
||||
"l" | "left" => Ok(AnchorAlignment::START),
|
||||
"c" | "center" => Ok(AnchorAlignment::CENTER),
|
||||
"r" | "right" => Ok(AnchorAlignment::END),
|
||||
_ => bail!(
|
||||
r#"couldn't parse '{}' as x-alignment. Must be one of "left", "center", "right""#,
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_y_alignment(s: &str) -> Result<AnchorAlignment> {
|
||||
match s {
|
||||
"t" | "top" => Ok(AnchorAlignment::START),
|
||||
"c" | "center" => Ok(AnchorAlignment::CENTER),
|
||||
"b" | "bottom" => Ok(AnchorAlignment::END),
|
||||
_ => bail!(
|
||||
r#"couldn't parse '{}' as y-alignment. Must be one of "top", "center", "bottom""#,
|
||||
s
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 {
|
||||
match self {
|
||||
AnchorAlignment::START => 0,
|
||||
AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2),
|
||||
AnchorAlignment::END => size_container - size_inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct AnchorPoint {
|
||||
x: AnchorAlignment,
|
||||
y: AnchorAlignment,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AnchorPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use AnchorAlignment::*;
|
||||
match (self.x, self.y) {
|
||||
(CENTER, CENTER) => write!(f, "center"),
|
||||
(x, y) => write!(
|
||||
f,
|
||||
"{} {}",
|
||||
match x {
|
||||
START => "left",
|
||||
CENTER => "center",
|
||||
END => "right",
|
||||
},
|
||||
match y {
|
||||
START => "top",
|
||||
CENTER => "center",
|
||||
END => "bottom",
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AnchorPoint {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "center" {
|
||||
Ok(AnchorPoint {
|
||||
x: AnchorAlignment::CENTER,
|
||||
y: AnchorAlignment::CENTER,
|
||||
})
|
||||
} else {
|
||||
let (first, second) = s
|
||||
.split_once(' ')
|
||||
.context("Failed to parse anchor: Must either be \"center\" or be formatted like \"top left\"")?;
|
||||
let x_y_result: Result<_> = try {
|
||||
AnchorPoint {
|
||||
x: AnchorAlignment::from_x_alignment(first)?,
|
||||
y: AnchorAlignment::from_y_alignment(second)?,
|
||||
}
|
||||
};
|
||||
x_y_result.or_else(|_| {
|
||||
Ok(AnchorPoint {
|
||||
x: AnchorAlignment::from_x_alignment(second)?,
|
||||
y: AnchorAlignment::from_y_alignment(first)?,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct EwwWindowGeometry {
|
||||
pub anchor_point: AnchorPoint,
|
||||
pub offset: Coords,
|
||||
pub size: Coords,
|
||||
}
|
||||
|
||||
impl EwwWindowGeometry {
|
||||
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
|
||||
Ok(EwwWindowGeometry {
|
||||
anchor_point: xml.parse_optional_attr("anchor")?.unwrap_or_default(),
|
||||
size: Coords {
|
||||
x: xml.parse_optional_attr("width")?.unwrap_or_default(),
|
||||
y: xml.parse_optional_attr("height")?.unwrap_or_default(),
|
||||
},
|
||||
offset: Coords {
|
||||
x: xml.parse_optional_attr("x")?.unwrap_or_default(),
|
||||
y: xml.parse_optional_attr("y")?.unwrap_or_default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EwwWindowGeometry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}-{} ({})", self.offset, self.size, self.anchor_point)
|
||||
}
|
||||
}
|
||||
|
||||
impl EwwWindowGeometry {
|
||||
/// Calculate the window rectangle given the configured window geometry
|
||||
pub fn get_window_rectangle(&self, screen_rect: gdk::Rectangle) -> gdk::Rectangle {
|
||||
let (offset_x, offset_y) = self.offset.relative_to(screen_rect.width, screen_rect.height);
|
||||
let (width, height) = self.size.relative_to(screen_rect.width, screen_rect.height);
|
||||
let x = screen_rect.x + offset_x + self.anchor_point.x.alignment_to_coordinate(width, screen_rect.width);
|
||||
let y = screen_rect.y + offset_y + self.anchor_point.y.alignment_to_coordinate(height, screen_rect.height);
|
||||
gdk::Rectangle { x, y, width, height }
|
||||
}
|
||||
}
|
|
@ -177,6 +177,20 @@ impl<'a, 'b> XmlElement<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn optional_attr<O, F: FnOnce(&str) -> Result<O>>(&self, key: &str, parse: F) -> Result<Option<O>> {
|
||||
match self.0.attribute(key) {
|
||||
Some(value) => parse(value).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_optional_attr<E, O: std::str::FromStr<Err = E>>(&self, key: &str) -> Result<Option<O>, E> {
|
||||
match self.0.attribute(key) {
|
||||
Some(value) => value.parse::<O>().map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn only_child(&self) -> Result<XmlNode> {
|
||||
with_text_pos_context! { self =>
|
||||
let mut children_iter = self.children();
|
||||
|
|
18
src/opts.rs
18
src/opts.rs
|
@ -4,7 +4,7 @@ use structopt::StructOpt;
|
|||
|
||||
use crate::{
|
||||
app,
|
||||
config::WindowName,
|
||||
config::{AnchorPoint, WindowName},
|
||||
value::{Coords, PrimitiveValue, VarName},
|
||||
};
|
||||
|
||||
|
@ -57,6 +57,10 @@ pub enum ActionWithServer {
|
|||
/// The size of the window to open
|
||||
#[structopt(short, long)]
|
||||
size: Option<Coords>,
|
||||
|
||||
/// Anchorpoint of the window, formatted like "top right"
|
||||
#[structopt(short, long)]
|
||||
anchor: Option<AnchorPoint>,
|
||||
},
|
||||
|
||||
/// Close the window with the given name
|
||||
|
@ -92,7 +96,17 @@ impl ActionWithServer {
|
|||
ActionWithServer::Update { mappings } => {
|
||||
app::EwwCommand::UpdateVars(mappings.into_iter().map(|x| x.into()).collect())
|
||||
}
|
||||
ActionWithServer::OpenWindow { window_name, pos, size } => app::EwwCommand::OpenWindow { window_name, pos, size },
|
||||
ActionWithServer::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
} => app::EwwCommand::OpenWindow {
|
||||
window_name,
|
||||
pos,
|
||||
size,
|
||||
anchor,
|
||||
},
|
||||
ActionWithServer::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name },
|
||||
ActionWithServer::KillServer => app::EwwCommand::KillServer,
|
||||
ActionWithServer::ShowState => {
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use anyhow::*;
|
||||
use derive_more::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)]
|
||||
pub enum NumWithUnit {
|
||||
#[display(fmt = "{}%", .0)]
|
||||
#[debug(fmt = "{}%", .0)]
|
||||
Percent(i32),
|
||||
#[display(fmt = "{}px", .0)]
|
||||
#[debug(fmt = "{}px", .0)]
|
||||
#[default]
|
||||
Pixels(i32),
|
||||
}
|
||||
|
||||
|
@ -18,7 +20,7 @@ impl FromStr for NumWithUnit {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref PATTERN: regex::Regex = regex::Regex::new("^(\\d+)(.*)$").unwrap();
|
||||
static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap();
|
||||
};
|
||||
|
||||
let captures = PATTERN.captures(s).with_context(|| format!("could not parse '{}'", s))?;
|
||||
|
@ -32,7 +34,7 @@ impl FromStr for NumWithUnit {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)]
|
||||
#[display(fmt = "{}X{}", x, y)]
|
||||
pub struct Coords {
|
||||
pub x: NumWithUnit,
|
||||
|
@ -64,6 +66,20 @@ impl Coords {
|
|||
y: y.parse().with_context(|| format!("Failed to parse '{}'", y))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// resolve the possibly relative coordinates relative to a given containers size
|
||||
pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
|
||||
(
|
||||
match self.x {
|
||||
NumWithUnit::Percent(n) => ((width as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => n,
|
||||
},
|
||||
match self.y {
|
||||
NumWithUnit::Percent(n) => ((height as f64 / 100.0) * n as f64) as i32,
|
||||
NumWithUnit::Pixels(n) => n,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Reference in a new issue