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}