livesplit_core/layout/editor/
mod.rs

1//! The editor module provides an editor for a [`Layout`]. The editor ensures that
2//! all the different invariants of the [`Layout`] objects are upheld no matter what
3//! kind of operations are being applied. It provides the current state of the
4//! editor as state objects that can be visualized by any kind of User
5//! Interface.
6
7use super::{Component, Layout, LayoutState};
8use crate::{settings::Value, timing::Snapshot};
9use core::result::Result as StdResult;
10
11mod state;
12
13pub use self::state::{Buttons as ButtonsState, State};
14
15/// The Layout Editor allows modifying a [`Layout`] while ensuring all the different
16/// invariants of the [`Layout`] objects are upheld no matter what kind of
17/// operations are being applied. It provides the current state of the editor as
18/// state objects that can be visualized by any kind of User Interface.
19pub struct Editor {
20    layout: Layout,
21    selected_component: usize,
22}
23
24/// Describes an Error that occurred while opening the Layout Editor.
25#[derive(Debug, snafu::Snafu)]
26pub enum Error {
27    /// The Layout Editor couldn't be opened because an empty layout with no
28    /// components was provided.
29    EmptyLayout,
30}
31
32/// The Result type for the Layout Editor.
33pub type Result<T> = StdResult<T, Error>;
34
35impl Editor {
36    /// Creates a new Layout Editor that modifies the Layout provided. Creation
37    /// of the Layout Editor fails when a Layout with no components is provided.
38    pub fn new(mut layout: Layout) -> Result<Self> {
39        if layout.components.is_empty() {
40            return Err(Error::EmptyLayout);
41        }
42
43        layout.remount();
44
45        Ok(Self {
46            layout,
47            selected_component: 0,
48        })
49    }
50
51    /// Closes the Layout Editor and gives back access to the modified Layout.
52    /// In case you want to implement a Cancel Button, just drop the Layout
53    /// object you get here.
54    #[allow(clippy::missing_const_for_fn)] // FIXME: Drop unsupported.
55    pub fn close(self) -> Layout {
56        self.layout
57    }
58
59    /// Calculates the layout's state based on the timer provided. You can use
60    /// this to visualize all of the components of a layout, while it is still
61    /// being edited by the Layout Editor.
62    pub fn layout_state(&mut self, timer: &Snapshot<'_>) -> LayoutState {
63        self.layout.state(timer)
64    }
65
66    /// Updates the layout's state based on the timer provided. You can use this
67    /// to visualize all of the components of a layout, while it is still being
68    /// edited by the Layout Editor.
69    pub fn update_layout_state(&mut self, state: &mut LayoutState, timer: &Snapshot<'_>) {
70        self.layout.update_state(state, timer)
71    }
72
73    /// Selects the component with the given index in order to modify its
74    /// settings. Only a single component is selected at any given time. You may
75    /// not provide an invalid index.
76    pub fn select(&mut self, index: usize) {
77        if index < self.layout.components.len() {
78            self.selected_component = index;
79        }
80    }
81
82    /// Adds the component provided to the end of the layout. The newly added
83    /// component becomes the selected component.
84    pub fn add_component<C: Into<Component>>(&mut self, component: C) {
85        self.selected_component = self.layout.components.len();
86        self.layout.push(component);
87    }
88
89    /// Checks if the currently selected component can be removed. If there's
90    /// only one component in the layout, it can't be removed.
91    pub fn can_remove_component(&self) -> bool {
92        // We need to ensure there's always at least one component.
93        self.layout.components.len() > 1
94    }
95
96    /// Removes the currently selected component, unless there's only one
97    /// component in the layout. The next component becomes the selected
98    /// component. If there's none, the previous component becomes the selected
99    /// component instead.
100    pub fn remove_component(&mut self) {
101        if self.can_remove_component() {
102            self.layout.components.remove(self.selected_component);
103            if self.selected_component >= self.layout.components.len() {
104                self.selected_component = self.layout.components.len() - 1;
105            }
106            self.layout.remount();
107        }
108    }
109
110    /// Checks if the currently selected component can be moved up. If the first
111    /// component is selected, it can't be moved up.
112    pub const fn can_move_component_up(&self) -> bool {
113        self.selected_component > 0
114    }
115
116    /// Moves the selected component up, unless the first component is selected.
117    pub fn move_component_up(&mut self) {
118        if self.can_move_component_up() {
119            self.layout
120                .components
121                .swap(self.selected_component, self.selected_component - 1);
122            self.selected_component -= 1;
123            self.layout.remount();
124        }
125    }
126
127    /// Checks if the currently selected component can be moved down. If the
128    /// last component is selected, it can't be moved down.
129    pub fn can_move_component_down(&self) -> bool {
130        self.selected_component < self.layout.components.len() - 1
131    }
132
133    /// Moves the selected component down, unless the last component is
134    /// selected.
135    pub fn move_component_down(&mut self) {
136        if self.can_move_component_down() {
137            self.layout
138                .components
139                .swap(self.selected_component, self.selected_component + 1);
140            self.selected_component += 1;
141            self.layout.remount();
142        }
143    }
144
145    /// Moves the selected component to the index provided. You may not provide
146    /// an invalid index.
147    pub fn move_component(&mut self, dst_index: usize) {
148        if dst_index < self.layout.components.len() {
149            while self.selected_component > dst_index {
150                self.move_component_up();
151            }
152            while self.selected_component < dst_index {
153                self.move_component_down();
154            }
155        }
156    }
157
158    /// Duplicates the currently selected component. The copy gets placed right
159    /// after the selected component and becomes the newly selected component.
160    pub fn duplicate_component(&mut self) {
161        let index = self.selected_component;
162        let new_index = index + 1;
163
164        let component = self.layout.components[index].clone();
165        self.layout.components.insert(new_index, component);
166
167        self.selected_component = new_index;
168        self.layout.remount();
169    }
170
171    /// Sets a setting's value of the selected component by its setting index
172    /// to the given value.
173    ///
174    /// # Panics
175    ///
176    /// This panics if the type of the value to be set is not compatible with
177    /// the type of the setting's value. A panic can also occur if the index of
178    /// the setting provided is out of bounds.
179    pub fn set_component_settings_value(&mut self, index: usize, value: Value) {
180        self.layout.components[self.selected_component].set_value(index, value);
181    }
182
183    /// Sets a setting's value of the general settings by its setting index to
184    /// the given value.
185    ///
186    /// # Panics
187    ///
188    /// This panics if the type of the value to be set is not compatible with
189    /// the type of the setting's value. A panic can also occur if the index of
190    /// the setting provided is out of bounds.
191    pub fn set_general_settings_value(&mut self, index: usize, value: Value) {
192        self.layout.general_settings_mut().set_value(index, value);
193    }
194}