CI/CD & Formatting (#6)

* Improve autoformatting guides, add editorconfig and add CI/CD
This commit is contained in:
Safin Singh 2020-10-13 09:57:50 -07:00 committed by elkowar
parent 4d6b38f3e1
commit c57713ca9a
17 changed files with 209 additions and 98 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

26
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: build
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Format
run: cargo build

26
.github/workflows/fmt.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: format
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
fmt:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Format
run: cargo fmt -- --check

26
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: test
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Set up
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt
- uses: actions/checkout@v2
- name: Test
run: cargo test --verbose

View file

@ -4,8 +4,6 @@ version = "0.1.0"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
gtk = { version = "0.9", features = [ "v3_16" ] } gtk = { version = "0.9", features = [ "v3_16" ] }
gdk = { version = "", features = ["v3_16"] } gdk = { version = "", features = ["v3_16"] }
@ -13,7 +11,6 @@ gio = { version = "", features = ["v2_44"] }
glib = { version = "", features = ["v2_44"] } glib = { version = "", features = ["v2_44"] }
gdk-pixbuf = "0.9" gdk-pixbuf = "0.9"
regex = "1" regex = "1"
try_match = "0.2.2" try_match = "0.2.2"
anyhow = "1.0" anyhow = "1.0"
@ -39,8 +36,5 @@ lazy_static = "1.4.0"
libc = "0.2" libc = "0.2"
ref-cast = "1.0" ref-cast = "1.0"
#thiserror = "1.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -1,15 +1,12 @@
# Table of Contents # Table of Contents
* [About](#org4ab08b6) - [About](#org4ab08b6)
* [Configuration](#org581ca61) - [Configuration](#org581ca61)
* [Example Config](#orgb769597) - [Example Config](#orgb769597)
* [Building](#orgbf66ce2) - [Building](#orgbf66ce2)
* [Prerequisites](#org727b3da) - [Prerequisites](#org727b3da)
* [Installation](#orgdd31739) - [Installation](#orgdd31739)
* [Usage](#org4a9b3c6) - [Usage](#org4a9b3c6)
<a id="org4ab08b6"></a> <a id="org4ab08b6"></a>
@ -17,17 +14,16 @@
Elkowar&rsquo;s Wacky Widgets is a standalone Widget System made in rust to add AwesomeWM like widgets to any WM Elkowar&rsquo;s Wacky Widgets is a standalone Widget System made in rust to add AwesomeWM like widgets to any WM
<a id="org581ca61"></a> <a id="org581ca61"></a>
# Configuration # Configuration
Eww&rsquo;s configuration should be placed in `~/.config/eww/eww.xml` and any `scss` styles you want to add should be put into `~/.config/eww/eww.scss`. Eww&rsquo;s configuration should be placed in `~/.config/eww/eww.xml` and any `scss` styles you want to add should be put into `~/.config/eww/eww.scss`.
<a id="orgb769597"></a> <a id="orgb769597"></a>
## Example Config ## Example Config
```xml ```xml
<eww> <eww>
<definitions> <definitions>
@ -66,17 +62,16 @@ Eww&rsquo;s configuration should be placed in `~/.config/eww/eww.xml` and any `s
# Building # Building
<a id="org727b3da"></a> <a id="org727b3da"></a>
## Prerequisites ## Prerequisites
- cargo with nightly toolchain - rustc
- cargo (nightly toolchain)
Rather than with your system package manager, I recommend installing it using [rustup](https://rustup.rs/), 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. as this makes it easy to use the nightly toolchain, which is necessary to build eww.
<a id="orgdd31739"></a> <a id="orgdd31739"></a>
## Installation ## Installation
@ -89,10 +84,8 @@ Build the Binary using -:
then copy the built binary from `./target/release` to anywhere in `$PATH` (example - `~/.local/bin`) then copy the built binary from `./target/release` to anywhere in `$PATH` (example - `~/.local/bin`)
<a id="org4a9b3c6"></a> <a id="org4a9b3c6"></a>
# Usage # Usage
Create a Config and then just do `eww`! Create a Config and then just do `eww`!

View file

@ -1,4 +1,13 @@
unstable_features=true unstable_features = true
fn_single_line=false fn_single_line = false
max_width=130 max_width = 130
reorder_impl_items = true
merge_imports = true
normalize_comments = true
use_field_init_shorthand = true
wrap_comments = true
combine_control_expr = false
condense_wildcard_suffixes = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true

View file

@ -3,9 +3,10 @@ use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::ops::Range; use std::ops::Range;
use crate::value::AttrValue; use crate::{
use crate::value::VarName; value::{AttrValue, VarName},
use crate::with_text_pos_context; with_text_pos_context,
};
use maplit::hashmap; use maplit::hashmap;
use std::collections::HashMap; use std::collections::HashMap;

View file

@ -1,12 +1,12 @@
use crate::util; use crate::{
use crate::value::PrimitiveValue; util,
use crate::value::VarName; value::{PrimitiveValue, VarName},
};
use anyhow::*; use anyhow::*;
use derive_more; use derive_more;
use element::*; use element::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::{collections::HashMap, fmt};
use std::fmt;
use util::Coords; use util::Coords;
use xml_ext::*; use xml_ext::*;
@ -134,12 +134,15 @@ impl EwwConfig {
pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> { pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets &self.widgets
} }
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> { pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
&self.windows &self.windows
} }
pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> { pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
&self.initial_variables &self.initial_variables
} }
pub fn get_script_vars(&self) -> &Vec<ScriptVar> { pub fn get_script_vars(&self) -> &Vec<ScriptVar> {
&self.script_vars &self.script_vars
} }
@ -211,6 +214,7 @@ impl Default for WindowStacking {
impl std::str::FromStr for WindowStacking { impl std::str::FromStr for WindowStacking {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
let s = s.to_lowercase(); let s = s.to_lowercase();
match s.as_str() { match s.as_str() {

View file

@ -141,6 +141,7 @@ impl<'a, 'b> XmlElement<'a, 'b> {
format!("<{} {}>", self.tag_name(), attrs) format!("<{} {}>", self.tag_name(), attrs)
} }
pub fn tag_name(&self) -> &str { pub fn tag_name(&self) -> &str {
self.0.tag_name().name() self.0.tag_name().name()
} }
@ -159,6 +160,7 @@ impl<'a, 'b> XmlElement<'a, 'b> {
.filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank())) .filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank()))
.map(XmlNode::from) .map(XmlNode::from)
} }
pub fn child_elements(&self) -> impl Iterator<Item = XmlElement> { pub fn child_elements(&self) -> impl Iterator<Item = XmlElement> {
self.0.children().filter(|child| child.is_element()).map(XmlElement) self.0.children().filter(|child| child.is_element()).map(XmlElement)
} }

View file

@ -1,14 +1,11 @@
use crate::config::WindowName; use crate::{config::WindowName, value::VarName};
use crate::value::VarName;
use anyhow::*; use anyhow::*;
use std::collections::HashMap; use std::{collections::HashMap, process::Command, sync::Arc};
use std::process::Command;
use std::sync::Arc;
use crate::value::{AttrValue, PrimitiveValue}; use crate::value::{AttrValue, PrimitiveValue};
/// Handler that get's executed to apply the necessary parts of the eww state to a gtk widget. /// Handler that get's executed to apply the necessary parts of the eww state to
/// These are created and initialized in EwwState::resolve. /// a gtk widget. These are created and initialized in EwwState::resolve.
pub struct StateChangeHandler { pub struct StateChangeHandler {
func: Box<dyn Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static>, func: Box<dyn Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static>,
constant_values: HashMap<String, PrimitiveValue>, constant_values: HashMap<String, PrimitiveValue>,
@ -57,7 +54,8 @@ impl EwwWindowState {
} }
} }
/// Stores the actual state of eww, including the variable state and the window-specific state-change handlers. /// Stores the actual state of eww, including the variable state and the
/// window-specific state-change handlers.
#[derive(Default)] #[derive(Default)]
pub struct EwwState { pub struct EwwState {
windows: HashMap<WindowName, EwwWindowState>, windows: HashMap<WindowName, EwwWindowState>,
@ -88,7 +86,8 @@ impl EwwState {
self.windows.clear(); self.windows.clear();
} }
/// Update the value of a variable, running all registered [StateChangeHandler]s. /// Update the value of a variable, running all registered
/// [StateChangeHandler]s.
pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> { pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> {
if !self.variables_state.contains_key(&key) { if !self.variables_state.contains_key(&key) {
bail!("Tried to set unknown variable '{}'", key); bail!("Tried to set unknown variable '{}'", key);
@ -111,7 +110,8 @@ impl EwwState {
/// resolves a value if possible, using the current eww_state /// resolves a value if possible, using the current eww_state
/// Expects there to be at max one level of nesting var_refs from local-env. /// Expects there to be at max one level of nesting var_refs from local-env.
/// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state. /// This means that no elements in the local_env may be var-refs into the
/// local_env again, but only into the global state.
pub fn resolve_once<'a>( pub fn resolve_once<'a>(
&'a self, &'a self,
local_env: &'a HashMap<VarName, AttrValue>, local_env: &'a HashMap<VarName, AttrValue>,
@ -134,9 +134,11 @@ impl EwwState {
} }
} }
/// Resolve takes a function that applies a set of fully resolved attribute values to it's gtk widget. /// Resolve takes a function that applies a set of fully resolved attribute
/// Expects there to be at max one level of nesting var_refs from local-env. /// values to it's gtk widget. Expects there to be at max one level of
/// This means that no elements in the local_env may be var-refs into the local_env again, but only into the global state. /// nesting var_refs from local-env. This means that no elements in the
/// local_env may be var-refs into the local_env again, but only into the
/// global state.
pub fn resolve<F: Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static + Clone>( pub fn resolve<F: Fn(HashMap<String, PrimitiveValue>) -> Result<()> + 'static + Clone>(
&mut self, &mut self,
window_name: &WindowName, window_name: &WindowName,
@ -144,9 +146,11 @@ impl EwwState {
mut needed_attributes: HashMap<String, AttrValue>, mut needed_attributes: HashMap<String, AttrValue>,
set_value: F, set_value: F,
) { ) {
// Resolve first collects all variable references and creates a set of unresolved attribute -> VarName pairs. // Resolve first collects all variable references and creates a set of
// additionally, all constant values are looked up and collected, including the values from the local environment // unresolved attribute -> VarName pairs. additionally, all constant values are
// These are then used to generate a StateChangeHandler, which is then executed and registered in the windows state. // looked up and collected, including the values from the local environment
// These are then used to generate a StateChangeHandler, which is then executed
// and registered in the windows state.
let result: Result<_> = try { let result: Result<_> = try {
let window_state = self let window_state = self

View file

@ -16,11 +16,12 @@ use ipc_channel::ipc;
use log; use log;
use pretty_env_logger; use pretty_env_logger;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::{
use std::path::{Path, PathBuf}; collections::HashMap,
path::{Path, PathBuf},
};
use structopt::StructOpt; use structopt::StructOpt;
use value::PrimitiveValue; use value::{PrimitiveValue, VarName};
use value::VarName;
pub mod app; pub mod app;
pub mod config; pub mod config;
@ -221,7 +222,8 @@ fn run_filewatch_thread<P: AsRef<Path>>(
Ok(hotwatch) Ok(hotwatch)
} }
/// detach the process from the terminal, also closing stdout and redirecting stderr into /dev/null /// detach the process from the terminal, also closing stdout and redirecting
/// stderr into /dev/null
fn do_detach() { fn do_detach() {
// detach from terminal // detach from terminal
let pid = unsafe { libc::fork() }; let pid = unsafe { libc::fork() };
@ -238,7 +240,7 @@ fn do_detach() {
libc::close(1); libc::close(1);
} }
} }
//close stderr to not spam output // close stderr to not spam output
if unsafe { libc::isatty(2) } != 0 { if unsafe { libc::isatty(2) } != 0 {
unsafe { unsafe {
let fd = libc::open(std::ffi::CString::new("/dev/null").unwrap().as_ptr(), libc::O_RDWR); let fd = libc::open(std::ffi::CString::new("/dev/null").unwrap().as_ptr(), libc::O_RDWR);

View file

@ -19,7 +19,8 @@ impl ScriptVarHandler {
}) })
} }
/// clears and stops the currently running poll handles, then opens the new ones as configured /// clears and stops the currently running poll handles, then opens the new
/// ones as configured
pub fn setup_command_poll_tasks(&mut self, config: &config::EwwConfig) -> Result<()> { pub fn setup_command_poll_tasks(&mut self, config: &config::EwwConfig) -> Result<()> {
log::info!("reloading handler for poll script vars"); log::info!("reloading handler for poll script vars");
self.poll_handles.iter().for_each(|handle| handle.stop()); self.poll_handles.iter().for_each(|handle| handle.stop());

View file

@ -3,8 +3,7 @@ use extend::ext;
use grass; use grass;
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::{fmt, path::Path};
use std::path::Path;
pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> { pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
let scss_content = std::fs::read_to_string(path)?; let scss_content = std::fs::read_to_string(path)?;
@ -14,7 +13,8 @@ pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
#[ext(pub, name = StringExt)] #[ext(pub, name = StringExt)]
impl<T: AsRef<str>> T { impl<T: AsRef<str>> T {
/// check if the string is empty after removing all linebreaks and trimming whitespace /// check if the string is empty after removing all linebreaks and trimming
/// whitespace
fn is_blank(self) -> bool { fn is_blank(self) -> bool {
self.as_ref().replace('\n', "").trim().is_empty() self.as_ref().replace('\n', "").trim().is_empty()
} }
@ -51,6 +51,7 @@ impl fmt::Display for Coords {
impl std::str::FromStr for Coords { impl std::str::FromStr for Coords {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
let (x, y) = s.split_once('x').ok_or_else(|| anyhow!("must be formatted like 200x500"))?; let (x, y) = s.split_once('x').ok_or_else(|| anyhow!("must be formatted like 200x500"))?;
Ok(Coords(x.parse()?, y.parse()?)) Ok(Coords(x.parse()?, y.parse()?))

View file

@ -4,8 +4,7 @@ use lazy_static::lazy_static;
use ref_cast::RefCast; use ref_cast::RefCast;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::{convert::TryFrom, fmt};
use std::fmt;
#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)] #[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)]
pub enum PrimitiveValue { pub enum PrimitiveValue {
@ -32,7 +31,8 @@ impl fmt::Debug for PrimitiveValue {
impl std::str::FromStr for PrimitiveValue { impl std::str::FromStr for PrimitiveValue {
type Err = anyhow::Error; type Err = anyhow::Error;
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string. /// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
fn from_str(s: &str) -> Result<Self> { fn from_str(s: &str) -> Result<Self> {
Ok(PrimitiveValue::parse_string(s)) Ok(PrimitiveValue::parse_string(s))
} }
@ -44,6 +44,7 @@ fn remove_surrounding(s: &str, surround: char) -> &str {
impl TryFrom<PrimitiveValue> for String { impl TryFrom<PrimitiveValue> for String {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_string() x.as_string()
} }
@ -51,6 +52,7 @@ impl TryFrom<PrimitiveValue> for String {
impl TryFrom<PrimitiveValue> for f64 { impl TryFrom<PrimitiveValue> for f64 {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_f64() x.as_f64()
} }
@ -58,6 +60,7 @@ impl TryFrom<PrimitiveValue> for f64 {
impl TryFrom<PrimitiveValue> for bool { impl TryFrom<PrimitiveValue> for bool {
type Error = anyhow::Error; type Error = anyhow::Error;
fn try_from(x: PrimitiveValue) -> Result<Self> { fn try_from(x: PrimitiveValue) -> Result<Self> {
x.as_bool() x.as_bool()
} }
@ -70,13 +73,15 @@ impl From<&str> for PrimitiveValue {
} }
impl PrimitiveValue { impl PrimitiveValue {
/// parses the value, trying to turn it into a number and a boolean first, before deciding that it is a string. /// parses the value, trying to turn it into a number and a boolean first,
/// before deciding that it is a string.
pub fn parse_string(s: &str) -> Self { pub fn parse_string(s: &str) -> Self {
s.parse() s.parse()
.map(PrimitiveValue::Number) .map(PrimitiveValue::Number)
.or_else(|_| s.parse().map(PrimitiveValue::Boolean)) .or_else(|_| s.parse().map(PrimitiveValue::Boolean))
.unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string())) .unwrap_or_else(|_| PrimitiveValue::String(remove_surrounding(s, '\'').to_string()))
} }
pub fn as_string(&self) -> Result<String> { pub fn as_string(&self) -> Result<String> {
match self { match self {
PrimitiveValue::String(x) => Ok(x.clone()), PrimitiveValue::String(x) => Ok(x.clone()),
@ -84,6 +89,7 @@ impl PrimitiveValue {
PrimitiveValue::Boolean(x) => Ok(format!("{}", x)), PrimitiveValue::Boolean(x) => Ok(format!("{}", x)),
} }
} }
pub fn as_f64(&self) -> Result<f64> { pub fn as_f64(&self) -> Result<f64> {
match self { match self {
PrimitiveValue::Number(x) => Ok(*x), PrimitiveValue::Number(x) => Ok(*x),
@ -93,6 +99,7 @@ impl PrimitiveValue {
_ => Err(anyhow!("{:?} is not an f64", &self)), _ => Err(anyhow!("{:?} is not an f64", &self)),
} }
} }
pub fn as_bool(&self) -> Result<bool> { pub fn as_bool(&self) -> Result<bool> {
match self { match self {
PrimitiveValue::Boolean(x) => Ok(*x), PrimitiveValue::Boolean(x) => Ok(*x),
@ -145,12 +152,14 @@ impl AttrValue {
_ => Err(anyhow!("{:?} is not a string", self)), _ => Err(anyhow!("{:?} is not a string", self)),
} }
} }
pub fn as_f64(&self) -> Result<f64> { pub fn as_f64(&self) -> Result<f64> {
match self { match self {
AttrValue::Concrete(x) => Ok(x.as_f64()?), AttrValue::Concrete(x) => Ok(x.as_f64()?),
_ => Err(anyhow!("{:?} is not an f64", self)), _ => Err(anyhow!("{:?} is not an f64", self)),
} }
} }
pub fn as_bool(&self) -> Result<bool> { pub fn as_bool(&self) -> Result<bool> {
match self { match self {
AttrValue::Concrete(x) => Ok(x.as_bool()?), AttrValue::Concrete(x) => Ok(x.as_bool()?),

View file

@ -1,6 +1,8 @@
use crate::config::{element, WindowName}; use crate::{
use crate::eww_state::*; config::{element, WindowName},
use crate::value::{AttrValue, VarName}; eww_state::*,
value::{AttrValue, VarName},
};
use anyhow::*; use anyhow::*;
use gtk::prelude::*; use gtk::prelude::*;
@ -11,8 +13,8 @@ pub mod widget_definitions;
const CMD_STRING_PLACEHODLER: &str = "{}"; const CMD_STRING_PLACEHODLER: &str = "{}";
/// Run a command that was provided as an attribute. This command may use a placeholder ('{}') /// Run a command that was provided as an attribute. This command may use a
/// which will be replaced by the value provided as [arg] /// placeholder ('{}') which will be replaced by the value provided as [arg]
pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) { pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg)); let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() { if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
@ -30,12 +32,14 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> {
} }
/// Generate a [gtk::Widget] from a [element::WidgetUse]. /// Generate a [gtk::Widget] from a [element::WidgetUse].
/// The widget_use may be using a builtin widget, or a custom [element::WidgetDefinition]. /// The widget_use may be using a builtin widget, or a custom
/// [element::WidgetDefinition].
/// ///
/// Also registers all the necessary state-change handlers in the eww_state. /// Also registers all the necessary state-change handlers in the eww_state.
/// ///
/// This may return `Err` in case there was an actual error while parsing or resolving the widget, /// This may return `Err` in case there was an actual error while parsing or
/// Or `Ok(None)` if the widget_use just didn't match any widget name. /// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
pub fn widget_use_to_gtk_widget( pub fn widget_use_to_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>, widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState, eww_state: &mut EwwState,
@ -48,18 +52,20 @@ pub fn widget_use_to_gtk_widget(
let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget { let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget {
builtin_gtk_widget builtin_gtk_widget
} else if let Some(def) = widget_definitions.get(widget.name.as_str()) { } else if let Some(def) = widget_definitions.get(widget.name.as_str()) {
//let mut local_env = local_env.clone(); // let mut local_env = local_env.clone();
// the attributes that are set on the widget need to be resolved as far as possible. // the attributes that are set on the widget need to be resolved as far as
// If an attribute is a variable reference, it must either reference a variable in the current local_env, or in the global state. // possible. If an attribute is a variable reference, it must either reference a
// As we are building widgets from the outer most to the most nested, we can resolve attributes at every step. // variable in the current local_env, or in the global state. As we are building
// This way, any definition that is affected by changes in the eww_state will be directly linked to the eww_state's value. // widgets from the outer most to the most nested, we can resolve attributes at
// Example: // every step. This way, any definition that is affected by changes in the
// foo="{{in_eww_state}}" => attr_in_child="{{foo}}" => attr_in_nested_child="{{attr_in_child}}" // eww_state will be directly linked to the eww_state's value. Example:
// will be resolved step by step. This code will first resolve attr_in_child to directly be attr_in_child={{in_eww_state}}. // foo="{{in_eww_state}}" => attr_in_child="{{foo}}" =>
// then, in the widget_use_to_gtk_widget call of that child element, // attr_in_nested_child="{{attr_in_child}}" will be resolved step by step. This
// attr_in_nested_child will again be resolved to point to the value of attr_in_child, // code will first resolve attr_in_child to directly be
// and thus: attr_in_nested_child="{{in_eww_state}}" // attr_in_child={{in_eww_state}}. then, in the widget_use_to_gtk_widget call of
// that child element, attr_in_nested_child will again be resolved to point to
// the value of attr_in_child, and thus: attr_in_nested_child="{{in_eww_state}}"
let resolved_widget_attr_env = widget let resolved_widget_attr_env = widget
.attrs .attrs
.clone() .clone()
@ -93,13 +99,14 @@ pub fn widget_use_to_gtk_widget(
Ok(gtk_widget) Ok(gtk_widget)
} }
/// build a [gtk::Widget] out of a [element::WidgetUse] that uses a **builtin widget**. /// build a [gtk::Widget] out of a [element::WidgetUse] that uses a **builtin
/// User defined widgets are handled by [widget_use_to_gtk_widget]. /// widget**. User defined widgets are handled by [widget_use_to_gtk_widget].
/// ///
/// Also registers all the necessary handlers in the `eww_state`. /// Also registers all the necessary handlers in the `eww_state`.
/// ///
/// This may return `Err` in case there was an actual error while parsing or resolving the widget, /// This may return `Err` in case there was an actual error while parsing or
/// Or `Ok(None)` if the widget_use just didn't match any widget name. /// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
fn build_builtin_gtk_widget( fn build_builtin_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>, widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState, eww_state: &mut EwwState,

View file

@ -1,15 +1,12 @@
use super::{run_command, BuilderArgs}; use super::{run_command, BuilderArgs};
use crate::config; use crate::{
use crate::eww_state; config, eww_state, resolve_block,
use crate::resolve_block; value::{AttrValue, PrimitiveValue},
use crate::value::{AttrValue, PrimitiveValue}; };
use anyhow::*; use anyhow::*;
use gtk::prelude::*; use gtk::{prelude::*, ImageExt};
use gtk::ImageExt;
use maplit::hashmap; use maplit::hashmap;
use std::cell::RefCell; use std::{cell::RefCell, path::Path, rc::Rc};
use std::path::Path;
use std::rc::Rc;
use gdk_pixbuf; use gdk_pixbuf;
@ -259,7 +256,8 @@ fn parse_align(o: &str) -> Result<gtk::Align> {
} }
fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) { fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {
// TODO it would be better to actually remove the connect_map after first map, but that would be highly annoying to implement... // TODO it would be better to actually remove the connect_map after first map,
// but that would be highly annoying to implement...
let is_first_map = std::rc::Rc::new(std::cell::RefCell::new(true)); let is_first_map = std::rc::Rc::new(std::cell::RefCell::new(true));
widget.connect_map(move |w| { widget.connect_map(move |w| {
if is_first_map.replace(false) { if is_first_map.replace(false) {