Skip to content

Commit

Permalink
better config builder and immutable config post build
Browse files Browse the repository at this point in the history
  • Loading branch information
sminez committed Jan 6, 2021
1 parent f8405e2 commit f59620f
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 147 deletions.
12 changes: 8 additions & 4 deletions examples/dynamic_workspaces/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ fn main() -> Result<()> {
SimpleLogger::init(LevelFilter::Debug, simplelog::Config::default())
.expect("failed to init logging");

let mut config = Config::default();
config.workspaces(vec!["main"]);
config.layouts(my_layouts());
let mut config_builder = Config::default().builder();
let config = config_builder
.workspaces(vec!["main"])
.layouts(my_layouts())
.build()
.unwrap();

let sp = Scratchpad::new("st", 0.8, 0.8);

let hooks: Hooks<_> = vec![
LayoutSymbolAsRootName::new(),
RemoveEmptyWorkspaces::new(config.workspaces.clone()),
RemoveEmptyWorkspaces::new(config.workspaces().clone()),
DefaultWorkspace::new("1term", "[side]", vec!["st"]),
DefaultWorkspace::new("2term", "[botm]", vec!["st", "st"]),
DefaultWorkspace::new("3term", "[side]", vec!["st", "st", "st"]),
Expand Down
4 changes: 2 additions & 2 deletions examples/minimal/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ fn main() -> Result<()> {
"M-Return" => run_external!("st");

refmap [ config.ws_range() ] in {
"M-{}" => focus_workspace [ index_selectors(config.workspaces.len()) ];
"M-S-{}" => client_to_workspace [ index_selectors(config.workspaces.len()) ];
"M-{}" => focus_workspace [ index_selectors(config.workspaces().len()) ];
"M-S-{}" => client_to_workspace [ index_selectors(config.workspaces().len()) ];
};
};

Expand Down
16 changes: 8 additions & 8 deletions examples/simple_config_with_hooks/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,9 @@ fn main() -> Result<()> {
SimpleLogger::init(LevelFilter::Debug, simplelog::Config::default())
.expect("failed to init logging");

// Config structs can be intiialised directly as all fields are public.
// A default config is provided which sets sensible (but minimal) values for each field.
let mut config = Config::default();

// Created at startup. See keybindings below for how to access them
config
let mut config_builder = Config::default().builder();
config_builder
.workspaces(vec!["1", "2", "3", "4", "5", "6", "7", "8", "9"])
// Windows with a matching WM_CLASS will always float
.floating_classes(vec!["dmenu", "dunst", "polybar"])
Expand All @@ -80,13 +77,16 @@ fn main() -> Result<()> {

// Layouts to be used on each workspace. Currently all workspaces have the same set of Layouts
// available to them, though they track modifications to n_main and ratio independently.
config.layouts(vec![
config_builder.layouts(vec![
Layout::new("[side]", LayoutConf::default(), side_stack, n_main, ratio),
Layout::new("[botm]", LayoutConf::default(), bottom_stack, n_main, ratio),
Layout::new("[papr]", follow_focus_conf, paper, n_main, ratio),
Layout::floating("[----]"),
]);

// Now build and validate the config
let config = config_builder.build().unwrap();

// NOTE: change these to programs that you have installed!
let my_program_launcher = "dmenu_run";
let my_file_manager = "thunar";
Expand Down Expand Up @@ -169,8 +169,8 @@ fn main() -> Result<()> {
// Each keybinding here will be templated in with the workspace index of each workspace,
// allowing for common workspace actions to be bound at once.
refmap [ config.ws_range() ] in {
"M-{}" => focus_workspace [ index_selectors(config.workspaces.len()) ];
"M-S-{}" => client_to_workspace [ index_selectors(config.workspaces.len()) ];
"M-{}" => focus_workspace [ index_selectors(config.workspaces().len()) ];
"M-S-{}" => client_to_workspace [ index_selectors(config.workspaces().len()) ];
};
};

Expand Down
235 changes: 104 additions & 131 deletions src/core/config.rs
Original file line number Diff line number Diff line change
@@ -1,147 +1,120 @@
//! User facing configuration of the penrose [WindowManager][crate::core::manager::WindowManager].
use crate::core::layout::{side_stack, Layout, LayoutConf};

use std::fmt;

/// The main user facing configuration details
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq)]
pub struct Config {
/// Default workspace names to use when initialising the WindowManager. Must have at least one element.
pub workspaces: Vec<String>,
/// WM_CLASS values that should always be treated as floating.
pub floating_classes: Vec<String>,
/// Default Layouts to be given to every workspace.
pub layouts: Vec<Layout>,
/// Focused boder color
pub focused_border: u32,
/// Unfocused boder color
pub unfocused_border: u32,
/// The width of window borders in pixels
pub border_px: u32,
/// The size of gaps between windows in pixels.
pub gap_px: u32,
/// The percentage change in main_ratio to be applied when increasing / decreasing.
pub main_ratio_step: f32,
/// Whether or not space should be reserved for a status bar
pub show_bar: bool,
/// True if the status bar should be at the top of the screen, false if it should be at the bottom
pub top_bar: bool,
/// Height of space reserved for status bars in pixels
pub bar_height: u32,
}

impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Config")
.field("workspaces", &self.workspaces)
.field("floating_classes", &self.floating_classes)
.field("layouts", &self.layouts)
.field("focused_border", &self.focused_border)
.field("unfocused_border", &self.unfocused_border)
.field("border_px", &self.border_px)
.field("gap_px", &self.gap_px)
.field("main_ratio_step", &self.main_ratio_step)
.field("show_bar", &self.show_bar)
.field("top_bar", &self.top_bar)
.field("bar_height", &self.bar_height)
.finish()
}
}

impl Default for Config {
fn default() -> Self {
Self {
workspaces: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
.iter()
.map(|s| s.to_string())
.collect(),
floating_classes: ["dmenu", "dunst"].iter().map(|s| s.to_string()).collect(),
layouts: vec![
Layout::new("[side]", LayoutConf::default(), side_stack, 1, 0.6),
Layout::floating("[----]"),
],
focused_border: 0xcc241d, // #cc241d
unfocused_border: 0x3c3836, // #3c3836
border_px: 2,
gap_px: 5,
main_ratio_step: 0.05,
show_bar: true,
top_bar: true,
bar_height: 18,
}
}
__with_builder_and_getters! {
/// The main user facing configuration details.
///
/// See [ConfigBuilder] for details of what can be overwritten.
///
/// # Example
/// ```
/// use penrose::Config;
///
/// let config = Config::default();
///
/// assert_eq!(config.border_px(), &2);
/// assert_eq!(config.focused_border(), &0xcc241d);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq)]
Config;

/// Builder struct for generating user [Config]
///
/// # Example
/// ```
/// use penrose::core::{config::Config, layout::{LayoutConf, Layout, side_stack, monocle}};
///
/// fn my_layouts() -> Vec<Layout> {
/// let mono_conf = LayoutConf {
/// follow_focus: true,
/// gapless: true,
/// ..Default::default()
/// };
/// let n_main = 1;
/// let ratio = 0.6;
///
/// vec![
/// Layout::new("[side]", LayoutConf::default(), side_stack, n_main, ratio),
/// Layout::new("[mono]", mono_conf, monocle, n_main, ratio),
/// ]
/// }
///
/// let mut config_builder = Config::default().builder();
/// let config = config_builder
/// .floating_classes(vec!["rofi", "dmenu", "dunst", "pinentry-gtk-2"])
/// .layouts(my_layouts())
/// .border_px(4)
/// .focused_border(0xebdbb2)
/// .build()
/// .expect("failed to build config");
/// ```
#[derive(Debug)]
ConfigBuilder;

/// the initial available workspaces.
///
/// # Constraints
/// You must provide at least one workspace per screen
VecImplInto workspaces: String; =>
["1", "2", "3", "4", "5", "6", "7", "8", "9"]
.iter()
.map(|s| s.to_string())
.collect();

/// the window classes that will always be considered floating
VecImplInto floating_classes: String; =>
["dmenu", "dunst"].iter().map(|s| s.to_string()).collect();

/// the [Layout] functions to be used by each [Workspace][crate::core::workspace::Workspace]
///
/// # Constraints
/// You must provide at least one layout function
Concrete layouts: Vec<Layout>; =>
vec![
Layout::new("[side]", LayoutConf::default(), side_stack, 1, 0.6),
Layout::floating("[----]"),
];

/// the focused border color as a hex literal
Concrete focused_border: u32; => 0xcc241d; // #cc241d
/// the unfocused border color as a hex literal
Concrete unfocused_border: u32; => 0x3c3836; // #3c3836
/// the border width of each window in pixels
Concrete border_px: u32; => 2;
/// the gap between tiled windows in pixels
Concrete gap_px: u32; => 5;
/// the percentage of the screen to grow the main region by when incrementing
Concrete main_ratio_step: f32; => 0.05;
/// whether or not space should be reserved for a status bar
Concrete show_bar: bool; => true;
/// whether or not the reserved space for a status bar is at the top of the sceen
Concrete top_bar: bool; => true;
/// the height of the space to be reserved for a status bar in pixels
Concrete bar_height: u32; => 18;
}

impl Config {
/// Create a range from 1 -> n_workspaces for use in keybindings
pub fn ws_range(&self) -> std::ops::Range<usize> {
1..(self.workspaces.len() + 1)
}
}

/// Set the workspaces field on this Config
pub fn workspaces(&mut self, val: Vec<impl Into<String>>) -> &mut Self {
self.workspaces = val.into_iter().map(|s| s.into()).collect();
self
}

/// Set the floating_classes field on this Config
pub fn floating_classes(&mut self, val: Vec<impl Into<String>>) -> &mut Self {
self.floating_classes = val.into_iter().map(|s| s.into()).collect();
self
}

/// Set the layouts field on this Config
pub fn layouts(&mut self, val: Vec<Layout>) -> &mut Self {
self.layouts = val;
self
}

/// Set the focused_border field on this Config
pub fn focused_border(&mut self, val: u32) -> &mut Self {
self.focused_border = val;
self
}

/// Set the unfocused_border field on this Config
pub fn unfocused_border(&mut self, val: u32) -> &mut Self {
self.unfocused_border = val;
self
}

/// Set the border_px field on this Config
pub fn border_px(&mut self, val: u32) -> &mut Self {
self.border_px = val;
self
}

/// Set the gap_px field on this Config
pub fn gap_px(&mut self, val: u32) -> &mut Self {
self.gap_px = val;
self
}

/// Set the main_ratio_step field on this Config
pub fn main_ratio_step(&mut self, val: f32) -> &mut Self {
self.main_ratio_step = val;
self
}
impl ConfigBuilder {
fn validate(&self) -> std::result::Result<(), String> {
if self.inner.workspaces.is_empty() {
return Err("Must supply at least one workspace name".into());
}

/// Set the show_bar field on this Config
pub fn show_bar(&mut self, val: bool) -> &mut Self {
self.show_bar = val;
self
}
if self.inner.layouts.is_empty() {
return Err("Must supply at least one layout function".into());
}

/// Set the top_bar field on this Config
pub fn top_bar(&mut self, val: bool) -> &mut Self {
self.top_bar = val;
self
}
if !(0.0..=1.0).contains(&self.inner.main_ratio_step) {
return Err("main_ratio_step must be in the range 0.0 -> 1.0".into());
}

/// Set the bar_height field on this Config
pub fn bar_height(&mut self, val: u32) -> &mut Self {
self.bar_height = val;
self
Ok(())
}
}
Loading

0 comments on commit f59620f

Please sign in to comment.