old school music tracker
1mod help_page;
2pub mod order_list;
3pub mod pattern;
4mod sample_list;
5mod song_directory_config_page;
6
7use help_page::HelpPage;
8use order_list::{OrderListPage, OrderListPageEvent};
9use pattern::{PatternPage, PatternPageEvent};
10use sample_list::SampleList;
11use song_directory_config_page::{SDCChange, SongDirectoryConfigPage};
12use winit::{
13 event::{KeyEvent, Modifiers},
14 event_loop::EventLoopProxy,
15 keyboard::{Key, NamedKey},
16};
17
18use crate::{
19 EventQueue, GlobalEvent,
20 coordinates::{CharPosition, CharRect, WINDOW_SIZE_CHARS},
21 draw_buffer::DrawBuffer,
22 pages::sample_list::SampleListEvent,
23};
24
25pub trait Page {
26 fn draw(&mut self, draw_buffer: &mut DrawBuffer);
27 fn draw_constant(&mut self, draw_buffer: &mut DrawBuffer);
28
29 fn process_key_event(
30 &mut self,
31 modifiers: &Modifiers,
32 key_event: &KeyEvent,
33 // please give me reborrowing for custom structs rustc :3
34 events: &mut EventQueue<'_>,
35 ) -> PageResponse;
36
37 #[cfg(feature = "accesskit")]
38 fn build_tree(
39 &self,
40 tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
41 ) -> crate::AccessResponse;
42}
43
44pub enum PageResponse {
45 RequestRedraw,
46 None,
47}
48
49#[derive(Clone, Copy, PartialEq, Eq, Debug)]
50pub enum PagesEnum {
51 Help,
52 SongDirectoryConfig,
53 Pattern,
54 OrderList,
55 SampleList,
56}
57
58#[derive(Debug, Clone)]
59pub enum PageEvent {
60 Sdc(SDCChange),
61 Pattern(PatternPageEvent),
62 OrderList(OrderListPageEvent),
63 SampleList(SampleListEvent),
64}
65
66impl PageEvent {
67 fn get_page(&self) -> PagesEnum {
68 match self {
69 PageEvent::Sdc(_) => PagesEnum::SongDirectoryConfig,
70 PageEvent::Pattern(_) => PagesEnum::Pattern,
71 PageEvent::OrderList(_) => PagesEnum::OrderList,
72 PageEvent::SampleList(_) => PagesEnum::SampleList,
73 }
74 }
75}
76
77pub struct AllPages {
78 help: HelpPage,
79 song_directory_config: SongDirectoryConfigPage,
80 pattern: PatternPage,
81 order_list: OrderListPage,
82 sample_list: SampleList,
83
84 const_draw_needed: bool,
85 current: PagesEnum,
86}
87
88impl AllPages {
89 pub fn new(proxy: EventLoopProxy<GlobalEvent>) -> Self {
90 AllPages {
91 help: HelpPage::new(),
92 song_directory_config: SongDirectoryConfigPage::new(),
93 pattern: PatternPage::new(proxy.clone()),
94 current: PagesEnum::SongDirectoryConfig,
95 order_list: OrderListPage::new(),
96 const_draw_needed: true,
97 sample_list: SampleList::new(proxy),
98 }
99 }
100
101 fn get_title(&self) -> &'static str {
102 match self.current {
103 PagesEnum::Help => "Help",
104 PagesEnum::SongDirectoryConfig => "Song Variables & Directory Configuration (F12)",
105 PagesEnum::Pattern => "Pattern Editor (F2)",
106 PagesEnum::OrderList => match self.order_list.mode() {
107 order_list::Mode::Volume => "Order List and Channel Volume (F11)",
108 order_list::Mode::Panning => "Order List and Panning (F11)",
109 },
110 PagesEnum::SampleList => "Sample List (F3)",
111 }
112 }
113
114 fn get_page(&self) -> &dyn Page {
115 match self.current {
116 PagesEnum::Help => &self.help,
117 PagesEnum::SongDirectoryConfig => &self.song_directory_config,
118 PagesEnum::Pattern => &self.pattern,
119 PagesEnum::OrderList => &self.order_list,
120 PagesEnum::SampleList => &self.sample_list,
121 }
122 }
123
124 fn get_page_mut(&mut self) -> &mut dyn Page {
125 match self.current {
126 PagesEnum::Help => &mut self.help,
127 PagesEnum::SongDirectoryConfig => &mut self.song_directory_config,
128 PagesEnum::Pattern => &mut self.pattern,
129 PagesEnum::OrderList => &mut self.order_list,
130 PagesEnum::SampleList => &mut self.sample_list,
131 }
132 }
133
134 /// requests a redraw if it is needed
135 pub fn switch_page(&mut self, next_page: PagesEnum) -> PageResponse {
136 if next_page != self.current {
137 // when switching to the OrderListPage, reset to the pan mode
138 if next_page == PagesEnum::OrderList {
139 self.order_list.reset_mode();
140 }
141 self.current = next_page;
142 self.request_draw_const();
143 PageResponse::RequestRedraw
144 } else {
145 PageResponse::None
146 }
147 }
148
149 pub fn request_draw_const(&mut self) {
150 self.const_draw_needed = true;
151 }
152
153 pub fn draw(&mut self, draw_buffer: &mut DrawBuffer) {
154 if self.const_draw_needed {
155 self.draw_constant(draw_buffer);
156 }
157 self.get_page_mut().draw(draw_buffer);
158 }
159
160 pub fn draw_constant(&mut self, draw_buffer: &mut DrawBuffer) {
161 // draw page title
162 let title = self.get_title();
163 let title_len = u8::try_from(title.len()).expect("title doesn't fit on the page");
164 let middle = WINDOW_SIZE_CHARS.0 / 2;
165 let str_start = middle - (title_len / 2);
166 draw_buffer.draw_string(title, CharPosition::new(str_start, 11), 0, 2);
167 const DOTTED: [u8; 8] = [0, 0, 0, 0b01010101, 0, 0, 0, 0];
168 draw_buffer.draw_rect(2, CharRect::new(11, 11, str_start - 1, str_start - 1));
169 draw_buffer.draw_rect(
170 2,
171 CharRect::new(11, 11, str_start + title_len, str_start + title_len),
172 );
173 for x in 1..=(str_start - 2) {
174 draw_buffer.draw_char(DOTTED, CharPosition::new(x, 11), 1, 2);
175 }
176 for x in (str_start + title_len + 1)..=(WINDOW_SIZE_CHARS.0 - 2) {
177 draw_buffer.draw_char(DOTTED, CharPosition::new(x, 11), 1, 2);
178 }
179 // draw page const
180 self.get_page_mut().draw_constant(draw_buffer);
181 self.const_draw_needed = false;
182 }
183
184 // add key_events for changing pages here
185 pub fn process_key_event(
186 &mut self,
187 modifiers: &Modifiers,
188 key_event: &KeyEvent,
189 events: &mut EventQueue<'_>,
190 ) -> PageResponse {
191 if key_event.state.is_pressed() && modifiers.state().is_empty() {
192 if key_event.logical_key == Key::Named(NamedKey::F1) {
193 self.switch_page(PagesEnum::Help);
194 return PageResponse::RequestRedraw;
195 } else if key_event.logical_key == Key::Named(NamedKey::F2) {
196 self.switch_page(PagesEnum::Pattern);
197 return PageResponse::RequestRedraw;
198 } else if key_event.logical_key == Key::Named(NamedKey::F11) {
199 if self.current == PagesEnum::OrderList {
200 self.order_list.switch_mode();
201 self.request_draw_const();
202 return PageResponse::RequestRedraw;
203 } else {
204 self.switch_page(PagesEnum::OrderList);
205 return PageResponse::RequestRedraw;
206 }
207 } else if key_event.logical_key == Key::Named(NamedKey::F12) {
208 self.switch_page(PagesEnum::SongDirectoryConfig);
209 return PageResponse::RequestRedraw;
210 } else if key_event.logical_key == Key::Named(NamedKey::F3) {
211 self.switch_page(PagesEnum::SampleList);
212 return PageResponse::RequestRedraw;
213 }
214 }
215
216 self.get_page_mut()
217 .process_key_event(modifiers, key_event, events)
218 }
219
220 pub fn process_page_event(
221 &mut self,
222 event: PageEvent,
223 events: &mut EventQueue<'_>,
224 ) -> PageResponse {
225 let page = event.get_page();
226 let response = match event {
227 PageEvent::Sdc(change) => self.song_directory_config.ui_change(change),
228 PageEvent::Pattern(event) => self.pattern.process_event(event, events),
229 PageEvent::OrderList(event) => self.order_list.process_event(event),
230 PageEvent::SampleList(event) => self.sample_list.process_event(event, events),
231 };
232
233 // if the page isn't shown a redraw isn't necessary
234 if page == self.current {
235 response
236 } else {
237 PageResponse::None
238 }
239 }
240
241 #[cfg(feature = "accesskit")]
242 pub fn build_tree(
243 &self,
244 tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
245 ) -> crate::AccessResponse {
246 self.get_page().build_tree(tree)
247 }
248
249 #[cfg(feature = "accesskit")]
250 pub fn process_accesskit_event(&mut self, event: accesskit::ActionRequest) -> PageResponse {
251 todo!()
252 }
253}