mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-21 08:11:30 +02:00
698 lines
22 KiB
Rust
698 lines
22 KiB
Rust
use crate::DEFAULT_PADDING;
|
|
use crate::bar::exec_powershell;
|
|
use crate::render::Grouping;
|
|
use crate::widgets::widget::WidgetConfig;
|
|
use eframe::egui::Pos2;
|
|
use eframe::egui::TextBuffer;
|
|
use eframe::egui::Vec2;
|
|
use komorebi_client::PathExt;
|
|
use komorebi_client::Rect;
|
|
use komorebi_client::SocketMessage;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// The `komorebi.bar.json` configuration file reference for `v0.1.41`
|
|
pub struct KomobarConfig {
|
|
/// Bar height
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 50)))]
|
|
pub height: Option<f32>,
|
|
/// Bar padding. Use one value for all sides or use a grouped padding for horizontal and/or
|
|
/// vertical definition which can each take a single value for a symmetric padding or two
|
|
/// values for each side, i.e.:
|
|
/// ```json
|
|
/// "padding": {
|
|
/// "horizontal": 10
|
|
/// }
|
|
/// ```
|
|
/// or:
|
|
/// ```json
|
|
/// "padding": {
|
|
/// "horizontal": [left, right]
|
|
/// }
|
|
/// ```
|
|
/// You can also set individual padding on each side like this:
|
|
/// ```json
|
|
/// "padding": {
|
|
/// "top": 10,
|
|
/// "bottom": 10,
|
|
/// "left": 10,
|
|
/// "right": 10,
|
|
/// }
|
|
/// ```
|
|
/// By default, padding is set to 10 on all sides.
|
|
pub padding: Option<Padding>,
|
|
/// Bar margin. Use one value for all sides or use a grouped margin for horizontal and/or
|
|
/// vertical definition which can each take a single value for a symmetric margin or two
|
|
/// values for each side, i.e.:
|
|
/// ```json
|
|
/// "margin": {
|
|
/// "horizontal": 10
|
|
/// }
|
|
/// ```
|
|
/// or:
|
|
/// ```json
|
|
/// "margin": {
|
|
/// "vertical": [top, bottom]
|
|
/// }
|
|
/// ```
|
|
/// You can also set individual margin on each side like this:
|
|
/// ```json
|
|
/// "margin": {
|
|
/// "top": 10,
|
|
/// "bottom": 10,
|
|
/// "left": 10,
|
|
/// "right": 10,
|
|
/// }
|
|
/// ```
|
|
/// By default, margin is set to 0 on all sides.
|
|
pub margin: Option<Margin>,
|
|
/// Bar positioning options
|
|
#[serde(alias = "viewport")]
|
|
pub position: Option<PositionConfig>,
|
|
/// Frame options (see: https://docs.rs/egui/latest/egui/containers/frame/struct.Frame.html)
|
|
pub frame: Option<FrameConfig>,
|
|
/// The monitor index or the full monitor options
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = MonitorConfigOrIndex::Index(0))))]
|
|
pub monitor: Option<MonitorConfigOrIndex>,
|
|
/// Font family
|
|
pub font_family: Option<String>,
|
|
/// Font size
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 12.5)))]
|
|
pub font_size: Option<f32>,
|
|
/// Scale of the icons relative to the font_size [[1.0-2.0]]
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 1.4)))]
|
|
pub icon_scale: Option<f32>,
|
|
/// Max label width before text truncation
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 400.0)))]
|
|
pub max_label_width: Option<f32>,
|
|
/// Theme
|
|
pub theme: Option<KomobarTheme>,
|
|
/// Alpha value for the color transparency [[0-255]]
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 200)))]
|
|
pub transparency_alpha: Option<u8>,
|
|
/// Spacing between widgets
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 10.0)))]
|
|
pub widget_spacing: Option<f32>,
|
|
/// Visual grouping for widgets
|
|
pub grouping: Option<Grouping>,
|
|
/// Options for mouse interaction on the bar
|
|
pub mouse: Option<MouseConfig>,
|
|
/// Left side widgets (ordered left-to-right)
|
|
pub left_widgets: Vec<WidgetConfig>,
|
|
/// Center widgets (ordered left-to-right)
|
|
pub center_widgets: Option<Vec<WidgetConfig>>,
|
|
/// Right side widgets (ordered left-to-right)
|
|
pub right_widgets: Vec<WidgetConfig>,
|
|
}
|
|
|
|
impl KomobarConfig {
|
|
pub fn aliases(raw: &str) {
|
|
let mut map = HashMap::new();
|
|
map.insert("position", ["viewport"]);
|
|
map.insert("end", ["inner_frame"]);
|
|
|
|
let mut display = false;
|
|
|
|
for aliases in map.values() {
|
|
for a in aliases {
|
|
if raw.contains(a) {
|
|
display = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if display {
|
|
println!(
|
|
"\nYour bar configuration file contains some options that have been renamed or deprecated:\n"
|
|
);
|
|
for (canonical, aliases) in map {
|
|
for alias in aliases {
|
|
if raw.contains(alias) {
|
|
println!(r#""{alias}" is now "{canonical}""#);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn show_all_icons_on_komorebi_workspace(widgets: &[WidgetConfig]) -> bool {
|
|
widgets
|
|
.iter()
|
|
.any(|w| matches!(w, WidgetConfig::Komorebi(config) if config.workspaces.is_some_and(|w| w.enable && w.display.is_some_and(|s| matches!(s,
|
|
WorkspacesDisplayFormat::AllIcons
|
|
| WorkspacesDisplayFormat::AllIconsAndText
|
|
| WorkspacesDisplayFormat::AllIconsAndTextOnSelected)))))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Position configuration
|
|
pub struct PositionConfig {
|
|
/// The desired starting position of the bar (0,0 = top left of the screen)
|
|
#[serde(alias = "position")]
|
|
pub start: Option<Position>,
|
|
/// The desired size of the bar from the starting position (usually monitor width x desired height)
|
|
#[serde(alias = "inner_size")]
|
|
pub end: Option<Position>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Frame configuration
|
|
pub struct FrameConfig {
|
|
/// Margin inside the painted frame
|
|
pub inner_margin: Position,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
#[serde(untagged)]
|
|
/// Monitor configuration or monitor index
|
|
pub enum MonitorConfigOrIndex {
|
|
/// The monitor index where you want the bar to show
|
|
Index(usize),
|
|
/// The full monitor options with the index and an optional work_area_offset
|
|
MonitorConfig(MonitorConfig),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Monitor configuration
|
|
pub struct MonitorConfig {
|
|
/// Komorebi monitor index of the monitor on which to render the bar
|
|
pub index: usize,
|
|
/// Automatically apply a work area offset for this monitor to accommodate the bar
|
|
pub work_area_offset: Option<Rect>,
|
|
}
|
|
|
|
pub type Padding = SpacingKind;
|
|
pub type Margin = SpacingKind;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
#[serde(untagged)]
|
|
// WARNING: To any developer messing with this code in the future: The order here matters!
|
|
// `Grouped` needs to come last, otherwise serde might mistaken an `IndividualSpacingConfig` for a
|
|
// `GroupedSpacingConfig` with both `vertical` and `horizontal` set to `None` ignoring the
|
|
// individual values.
|
|
/// Spacing kind
|
|
pub enum SpacingKind {
|
|
/// Spacing applied to all sides
|
|
All(f32),
|
|
/// Individual spacing applied to each side
|
|
Individual(IndividualSpacingConfig),
|
|
/// Grouped vertical and horizontal spacing
|
|
Grouped(GroupedSpacingConfig),
|
|
}
|
|
|
|
impl SpacingKind {
|
|
pub fn to_individual(&self, default: f32) -> IndividualSpacingConfig {
|
|
match self {
|
|
SpacingKind::All(m) => IndividualSpacingConfig::all(*m),
|
|
SpacingKind::Grouped(grouped_spacing_config) => {
|
|
let vm = grouped_spacing_config.vertical.as_ref().map_or(
|
|
IndividualSpacingConfig::vertical(default),
|
|
|vm| match vm {
|
|
GroupedSpacingOptions::Symmetrical(m) => {
|
|
IndividualSpacingConfig::vertical(*m)
|
|
}
|
|
GroupedSpacingOptions::Split(tm, bm) => {
|
|
IndividualSpacingConfig::vertical(*tm).bottom(*bm)
|
|
}
|
|
},
|
|
);
|
|
let hm = grouped_spacing_config.horizontal.as_ref().map_or(
|
|
IndividualSpacingConfig::horizontal(default),
|
|
|hm| match hm {
|
|
GroupedSpacingOptions::Symmetrical(m) => {
|
|
IndividualSpacingConfig::horizontal(*m)
|
|
}
|
|
GroupedSpacingOptions::Split(lm, rm) => {
|
|
IndividualSpacingConfig::horizontal(*lm).right(*rm)
|
|
}
|
|
},
|
|
);
|
|
IndividualSpacingConfig {
|
|
top: vm.top,
|
|
bottom: vm.bottom,
|
|
left: hm.left,
|
|
right: hm.right,
|
|
}
|
|
}
|
|
SpacingKind::Individual(m) => *m,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Grouped vertical and horizontal spacing
|
|
pub struct GroupedSpacingConfig {
|
|
/// Vertical grouped spacing
|
|
pub vertical: Option<GroupedSpacingOptions>,
|
|
/// Horizontal grouped spacing
|
|
pub horizontal: Option<GroupedSpacingOptions>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
#[serde(untagged)]
|
|
/// Grouped spacing options
|
|
pub enum GroupedSpacingOptions {
|
|
/// Symmetrical grouped spacing
|
|
Symmetrical(f32),
|
|
/// Split grouped spacing
|
|
Split(f32, f32),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Individual spacing configuration
|
|
pub struct IndividualSpacingConfig {
|
|
/// Spacing for the top
|
|
pub top: f32,
|
|
/// Spacing for the bottom
|
|
pub bottom: f32,
|
|
/// Spacing for the left
|
|
pub left: f32,
|
|
/// Spacing for the right
|
|
pub right: f32,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl IndividualSpacingConfig {
|
|
pub const ZERO: Self = IndividualSpacingConfig {
|
|
top: 0.0,
|
|
bottom: 0.0,
|
|
left: 0.0,
|
|
right: 0.0,
|
|
};
|
|
|
|
pub fn all(value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
top: value,
|
|
bottom: value,
|
|
left: value,
|
|
right: value,
|
|
}
|
|
}
|
|
|
|
pub fn horizontal(value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
top: 0.0,
|
|
bottom: 0.0,
|
|
left: value,
|
|
right: value,
|
|
}
|
|
}
|
|
|
|
pub fn vertical(value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
top: value,
|
|
bottom: value,
|
|
left: 0.0,
|
|
right: 0.0,
|
|
}
|
|
}
|
|
|
|
pub fn top(self, value: f32) -> Self {
|
|
IndividualSpacingConfig { top: value, ..self }
|
|
}
|
|
|
|
pub fn bottom(self, value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
bottom: value,
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub fn left(self, value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
left: value,
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub fn right(self, value: f32) -> Self {
|
|
IndividualSpacingConfig {
|
|
right: value,
|
|
..self
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_individual_spacing(
|
|
default: f32,
|
|
spacing: &Option<SpacingKind>,
|
|
) -> IndividualSpacingConfig {
|
|
spacing
|
|
.as_ref()
|
|
.map_or(IndividualSpacingConfig::all(default), |s| {
|
|
s.to_individual(default)
|
|
})
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
#[serde(untagged)]
|
|
/// Mouse message
|
|
pub enum MouseMessage {
|
|
/// Send a message to the komorebi client.
|
|
/// By default, a batch of messages are sent in the following order:
|
|
/// FocusMonitorAtCursor =>
|
|
/// MouseFollowsFocus(false) =>
|
|
/// {message} =>
|
|
/// MouseFollowsFocus({original.value})
|
|
///
|
|
/// Example:
|
|
/// ```json
|
|
/// "on_extra2_click": {
|
|
/// "message": {
|
|
/// "type": "NewWorkspace"
|
|
/// }
|
|
/// },
|
|
/// ```
|
|
/// or:
|
|
/// ```json
|
|
/// "on_middle_click": {
|
|
/// "focus_monitor_at_cursor": false,
|
|
/// "ignore_mouse_follows_focus": false,
|
|
/// "message": {
|
|
/// "type": "TogglePause"
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
/// or:
|
|
/// ```json
|
|
/// "on_scroll_up": {
|
|
/// "message": {
|
|
/// "type": "CycleFocusWorkspace",
|
|
/// "content": "Previous"
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
Komorebi(KomorebiMouseMessage),
|
|
/// Execute a custom command.
|
|
/// CMD (%variable%), Bash ($variable) and PowerShell ($Env:variable) variables will be resolved.
|
|
/// Example: `komorebic toggle-pause`
|
|
Command(String),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Komorebi socket mouse message
|
|
pub struct KomorebiMouseMessage {
|
|
/// Send the FocusMonitorAtCursor message
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
|
|
pub focus_monitor_at_cursor: Option<bool>,
|
|
/// Wrap the {message} with a MouseFollowsFocus(false) and MouseFollowsFocus({original.value}) message
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = true)))]
|
|
pub ignore_mouse_follows_focus: Option<bool>,
|
|
/// The message to send to the komorebi client
|
|
pub message: komorebi_client::SocketMessage,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Mouse configuration
|
|
pub struct MouseConfig {
|
|
/// Command to send on primary/left double button click
|
|
pub on_primary_double_click: Option<MouseMessage>,
|
|
/// Command to send on secondary/right button click
|
|
pub on_secondary_click: Option<MouseMessage>,
|
|
/// Command to send on middle button click
|
|
pub on_middle_click: Option<MouseMessage>,
|
|
/// Command to send on extra1/back button click
|
|
pub on_extra1_click: Option<MouseMessage>,
|
|
/// Command to send on extra2/forward button click
|
|
pub on_extra2_click: Option<MouseMessage>,
|
|
|
|
/// Defines how many points a user needs to scroll vertically to make a "tick" on a mouse/touchpad/touchscreen
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
|
|
pub vertical_scroll_threshold: Option<f32>,
|
|
/// Command to send on scrolling up (every tick)
|
|
pub on_scroll_up: Option<MouseMessage>,
|
|
/// Command to send on scrolling down (every tick)
|
|
pub on_scroll_down: Option<MouseMessage>,
|
|
|
|
/// Defines how many points a user needs to scroll horizontally to make a "tick" on a mouse/touchpad/touchscreen
|
|
#[cfg_attr(feature = "schemars", schemars(extend("default" = 30.0)))]
|
|
pub horizontal_scroll_threshold: Option<f32>,
|
|
/// Command to send on scrolling left (every tick)
|
|
pub on_scroll_left: Option<MouseMessage>,
|
|
/// Command to send on scrolling right (every tick)
|
|
pub on_scroll_right: Option<MouseMessage>,
|
|
}
|
|
|
|
impl MouseConfig {
|
|
pub fn has_command(&self) -> bool {
|
|
[
|
|
&self.on_primary_double_click,
|
|
&self.on_secondary_click,
|
|
&self.on_middle_click,
|
|
&self.on_extra1_click,
|
|
&self.on_extra2_click,
|
|
&self.on_scroll_up,
|
|
&self.on_scroll_down,
|
|
&self.on_scroll_left,
|
|
&self.on_scroll_right,
|
|
]
|
|
.iter()
|
|
.any(|opt| matches!(opt, Some(MouseMessage::Command(_))))
|
|
}
|
|
}
|
|
|
|
impl MouseMessage {
|
|
pub fn execute(&self, mouse_follows_focus: bool) {
|
|
match self {
|
|
MouseMessage::Komorebi(config) => {
|
|
let mut messages = Vec::new();
|
|
|
|
if config.focus_monitor_at_cursor.unwrap_or(true) {
|
|
messages.push(SocketMessage::FocusMonitorAtCursor);
|
|
}
|
|
|
|
if config.ignore_mouse_follows_focus.unwrap_or(true) {
|
|
messages.push(SocketMessage::MouseFollowsFocus(false));
|
|
messages.push(config.message.clone());
|
|
messages.push(SocketMessage::MouseFollowsFocus(mouse_follows_focus));
|
|
} else {
|
|
messages.push(config.message.clone());
|
|
}
|
|
|
|
tracing::debug!("Sending messages: {messages:?}");
|
|
|
|
if komorebi_client::send_batch(messages).is_err() {
|
|
tracing::error!("could not send commands");
|
|
}
|
|
}
|
|
MouseMessage::Command(cmd) => {
|
|
tracing::debug!("Executing command: {}", cmd);
|
|
|
|
let cmd_no_env = cmd.replace_env();
|
|
|
|
if exec_powershell(cmd_no_env.to_str().expect("Invalid command")).is_err() {
|
|
tracing::error!("Failed to execute '{}'", cmd);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
impl KomobarConfig {
|
|
pub fn read(path: &PathBuf) -> color_eyre::Result<Self> {
|
|
let content = std::fs::read_to_string(path)?;
|
|
let mut value: Self = match path.extension().unwrap().to_string_lossy().as_str() {
|
|
"json" => serde_json::from_str(&content)?,
|
|
_ => panic!("unsupported format"),
|
|
};
|
|
|
|
if value.frame.is_none() {
|
|
value.frame = Some(FrameConfig {
|
|
inner_margin: Position {
|
|
x: DEFAULT_PADDING,
|
|
y: DEFAULT_PADDING,
|
|
},
|
|
});
|
|
}
|
|
|
|
Ok(value)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Position
|
|
pub struct Position {
|
|
/// X coordinate
|
|
pub x: f32,
|
|
/// Y coordinate
|
|
pub y: f32,
|
|
}
|
|
|
|
impl From<Position> for Vec2 {
|
|
fn from(value: Position) -> Self {
|
|
Self {
|
|
x: value.x,
|
|
y: value.y,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Position> for Pos2 {
|
|
fn from(value: Position) -> Self {
|
|
Self {
|
|
x: value.x,
|
|
y: value.y,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub use komorebi_themes::KomobarTheme;
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Label prefix
|
|
pub enum LabelPrefix {
|
|
/// Show no prefix
|
|
None,
|
|
/// Show an icon
|
|
Icon,
|
|
/// Show text
|
|
Text,
|
|
/// Show an icon and text
|
|
IconAndText,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Display format
|
|
pub enum DisplayFormat {
|
|
/// Show only icon
|
|
Icon,
|
|
/// Show only text
|
|
Text,
|
|
/// Show an icon and text for the selected element, and text on the rest
|
|
TextAndIconOnSelected,
|
|
/// Show both icon and text
|
|
IconAndText,
|
|
/// Show an icon and text for the selected element, and icons on the rest
|
|
IconAndTextOnSelected,
|
|
}
|
|
|
|
macro_rules! extend_enum {
|
|
($(#[$type_meta:meta])* $existing_enum:ident, $new_enum:ident, { $($(#[$meta:meta])* $variant:ident),* $(,)? }) => {
|
|
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
$(#[$type_meta])*
|
|
pub enum $new_enum {
|
|
// Add new variants
|
|
$(
|
|
$(#[$meta])*
|
|
$variant,
|
|
)*
|
|
// Include a variant that wraps the existing enum and flatten it when deserializing
|
|
#[serde(untagged)]
|
|
Existing($existing_enum),
|
|
}
|
|
|
|
// Implement From for the existing enum
|
|
impl From<$existing_enum> for $new_enum {
|
|
fn from(value: $existing_enum) -> Self {
|
|
$new_enum::Existing(value)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
extend_enum!(
|
|
/// Workspaces display format
|
|
DisplayFormat, WorkspacesDisplayFormat, {
|
|
/// Show all icons only
|
|
AllIcons,
|
|
/// Show both all icons and text
|
|
AllIconsAndText,
|
|
/// Show all icons and text for the selected element, and all icons on the rest
|
|
AllIconsAndTextOnSelected,
|
|
});
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// Media widget display format
|
|
pub enum MediaDisplayFormat {
|
|
/// Show only the media info icon
|
|
Icon,
|
|
/// Show only the media info text (artist - title)
|
|
Text,
|
|
/// Show both icon and text
|
|
IconAndText,
|
|
/// Show only the control buttons (previous, play/pause, next)
|
|
ControlsOnly,
|
|
/// Show icon with control buttons
|
|
IconAndControls,
|
|
/// Show text with control buttons
|
|
TextAndControls,
|
|
/// Show icon, text, and control buttons
|
|
Full,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use serde_json::json;
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub enum OriginalDisplayFormat {
|
|
/// Show None Of The Things
|
|
NoneOfTheThings,
|
|
}
|
|
|
|
extend_enum!(OriginalDisplayFormat, ExtendedDisplayFormat, {
|
|
/// Show Some Of The Things
|
|
SomeOfTheThings,
|
|
});
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct ExampleConfig {
|
|
#[allow(unused)]
|
|
format: ExtendedDisplayFormat,
|
|
}
|
|
|
|
#[test]
|
|
pub fn extend_new_variant() {
|
|
let raw = json!({
|
|
"format": "SomeOfTheThings",
|
|
})
|
|
.to_string();
|
|
|
|
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
|
|
}
|
|
|
|
#[test]
|
|
pub fn extend_existing_variant() {
|
|
let raw = json!({
|
|
"format": "NoneOfTheThings",
|
|
})
|
|
.to_string();
|
|
|
|
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_ok())
|
|
}
|
|
|
|
#[test]
|
|
pub fn extend_invalid_variant() {
|
|
let raw = json!({
|
|
"format": "ALLOFTHETHINGS",
|
|
})
|
|
.to_string();
|
|
|
|
assert!(serde_json::from_str::<ExampleConfig>(&raw).is_err())
|
|
}
|
|
}
|