ui.rs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. use std::{collections::HashMap, rc::Rc};
  2. use kahlo::math::{PixelPoint, PixelSize};
  3. use crate::{
  4. component::Component,
  5. font::FontStore,
  6. input::MouseButton,
  7. layout::{LayoutCache, LayoutNode},
  8. theme::Theme,
  9. window::{Window, WindowBuilder, WindowComponent, WindowEvent, WindowStateAccess},
  10. };
  11. #[derive(Debug)]
  12. pub enum UIControlMsg {
  13. Terminate,
  14. }
  15. pub trait UIComponent: Sized + Component<ParentMsg = UIControlMsg> {
  16. fn init(&mut self, ui_handle: &mut UIHandle);
  17. fn poll(&mut self, ui_handle: &mut UIHandle) -> Vec<UIControlMsg>;
  18. }
  19. pub(crate) struct UIState {
  20. pub(crate) layout_cache: std::rc::Rc<LayoutCache>,
  21. pub(crate) window_states:
  22. HashMap<winit::window::WindowId, std::rc::Weak<dyn WindowStateAccess>>,
  23. pub(crate) fontstore: FontStore,
  24. pub(crate) theme: Theme,
  25. }
  26. pub struct UIHandle<'l> {
  27. pub(crate) state: &'l mut UIState,
  28. pub(crate) eloop: &'l winit::event_loop::ActiveEventLoop,
  29. }
  30. impl<'l> UIHandle<'l> {
  31. pub fn window_builder<'r>(&'r mut self) -> WindowBuilder<'r, 'l> {
  32. WindowBuilder::new(self)
  33. }
  34. pub fn new_layout_node(&self) -> LayoutNode {
  35. LayoutNode::new(self.state.layout_cache.clone())
  36. }
  37. pub fn theme(&self) -> &Theme {
  38. &self.state.theme
  39. }
  40. pub fn lookup_font(&self, q: &fontdb::Query) -> Rc<fontdue::Font> {
  41. self.state
  42. .fontstore
  43. .query_for_font(q)
  44. .expect("no matching font found")
  45. }
  46. }
  47. pub struct UI<UIC: UIComponent> {
  48. state: UIState,
  49. event_loop: Option<winit::event_loop::EventLoop<()>>,
  50. ui_component: UIC,
  51. autoclose: bool,
  52. initialized: bool,
  53. }
  54. impl<UIC: UIComponent> UI<UIC> {
  55. pub fn new(uic: UIC, fontstore: FontStore) -> Self {
  56. let lcache = LayoutCache::new();
  57. let eloop = winit::event_loop::EventLoop::builder().build().unwrap();
  58. eloop.set_control_flow(winit::event_loop::ControlFlow::Wait);
  59. Self {
  60. state: UIState {
  61. layout_cache: lcache,
  62. window_states: Default::default(),
  63. theme: Theme::build_default(&fontstore),
  64. fontstore,
  65. },
  66. event_loop: Some(eloop),
  67. ui_component: uic,
  68. autoclose: true,
  69. initialized: false,
  70. }
  71. }
  72. pub fn disable_autoclose(mut self) -> Self {
  73. self.autoclose = false;
  74. self
  75. }
  76. pub fn ui_component(&self) -> &UIC {
  77. &self.ui_component
  78. }
  79. pub fn ui_component_mut(&mut self) -> &mut UIC {
  80. &mut self.ui_component
  81. }
  82. /// Public helper function, delivers a message to the UI component and then processes whatever
  83. /// control messages result.
  84. pub fn deliver_msg(&mut self, msg: UIC::Msg) {
  85. let cmsgs = self.ui_component.process(msg);
  86. self.process_control_msgs(cmsgs.into_iter());
  87. }
  88. fn process_control_msgs(&mut self, msgs: impl Iterator<Item = UIControlMsg>) {
  89. for cmsg in msgs {
  90. match cmsg {
  91. UIControlMsg::Terminate => {
  92. todo!()
  93. // self.ui_running = false;
  94. }
  95. }
  96. }
  97. }
  98. pub fn run(mut self) {
  99. let eloop = self.event_loop.take().unwrap();
  100. eloop.run_app(&mut self).unwrap();
  101. }
  102. fn pump_events(&mut self, eloop: &winit::event_loop::ActiveEventLoop) {
  103. let evts = self.ui_component.poll(&mut UIHandle {
  104. eloop,
  105. state: &mut self.state,
  106. });
  107. for evt in evts.into_iter() {
  108. match evt {
  109. UIControlMsg::Terminate => eloop.exit(),
  110. }
  111. }
  112. }
  113. }
  114. impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
  115. fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
  116. if !self.initialized {
  117. self.ui_component.init(&mut UIHandle {
  118. eloop: event_loop,
  119. state: &mut self.state,
  120. });
  121. self.initialized = true;
  122. }
  123. }
  124. fn window_event(
  125. &mut self,
  126. event_loop: &winit::event_loop::ActiveEventLoop,
  127. window_id: winit::window::WindowId,
  128. event: winit::event::WindowEvent,
  129. ) {
  130. let Some(wsa) = self
  131. .state
  132. .window_states
  133. .get(&window_id)
  134. .and_then(std::rc::Weak::upgrade)
  135. else {
  136. println!("superfluous event: {event:?}");
  137. return;
  138. };
  139. match event {
  140. winit::event::WindowEvent::CloseRequested => {
  141. wsa.push_event(WindowEvent::CloseRequested);
  142. self.pump_events(event_loop);
  143. }
  144. winit::event::WindowEvent::Destroyed => {
  145. self.state.window_states.remove(&window_id);
  146. }
  147. winit::event::WindowEvent::RedrawRequested => {
  148. wsa.redraw(&UIHandle {
  149. eloop: event_loop,
  150. state: &mut self.state,
  151. });
  152. self.pump_events(event_loop);
  153. }
  154. winit::event::WindowEvent::CursorMoved { position, .. } => {
  155. wsa.update_mouse_pos(PixelPoint::new(position.x as i32, position.y as i32));
  156. wsa.request_redraw();
  157. self.pump_events(event_loop);
  158. }
  159. winit::event::WindowEvent::MouseInput { state, button, .. } => {
  160. let button = match button {
  161. winit::event::MouseButton::Left => MouseButton::Left,
  162. winit::event::MouseButton::Right => MouseButton::Right,
  163. _ => return,
  164. };
  165. wsa.update_mouse_button(button, state.is_pressed());
  166. wsa.request_redraw();
  167. self.pump_events(event_loop);
  168. }
  169. winit::event::WindowEvent::Resized(newsize) => {
  170. wsa.notify_resize(PixelSize::new(newsize.width as i32, newsize.height as i32));
  171. wsa.request_redraw();
  172. }
  173. winit::event::WindowEvent::KeyboardInput {
  174. device_id,
  175. event,
  176. is_synthetic,
  177. } => {
  178. if event.state == winit::event::ElementState::Pressed {
  179. if let Some(text) = event.text {
  180. wsa.update_text_input(text.as_str());
  181. }
  182. wsa.push_keypress(event.logical_key);
  183. self.pump_events(event_loop);
  184. wsa.request_redraw();
  185. }
  186. }
  187. _ => {}
  188. }
  189. }
  190. }
  191. /// Opinionated base UI component.
  192. ///
  193. /// Most of the time, this will be the UI component you will want to use: a single main window,
  194. /// passing UI events back from the window component. Unless you want multiple windows, or need to
  195. /// use a custom font directory, there is no reason to not use this component.
  196. pub struct OpinionatedUIComponent<'l, WC: WindowComponent> {
  197. window: Option<Window<WC>>,
  198. builder: Option<Box<dyn FnOnce(&mut UIHandle) -> WC + 'l>>,
  199. }
  200. impl<'l, WC: WindowComponent> OpinionatedUIComponent<'l, WC> {}
  201. impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> Component
  202. for OpinionatedUIComponent<'l, WC>
  203. {
  204. type ParentMsg = UIControlMsg;
  205. type Msg = UIControlMsg;
  206. fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
  207. vec![msg]
  208. }
  209. }
  210. impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> UIComponent
  211. for OpinionatedUIComponent<'l, WC>
  212. {
  213. fn init(&mut self, ui_handle: &mut UIHandle) {
  214. if let Some(builder) = self.builder.take() {
  215. self.window = Some(ui_handle.window_builder().build(builder));
  216. }
  217. }
  218. fn poll(&mut self, uih: &mut UIHandle) -> Vec<UIControlMsg> {
  219. if let Some(window) = self.window.as_mut() {
  220. window.poll(uih)
  221. } else {
  222. vec![]
  223. }
  224. }
  225. }
  226. /// Build an [`OpinionatedUIComponent`] for a given [`WindowComponent`] that passes
  227. /// [`UIControlMsg`]s back to its parent component and uses system fonts.
  228. pub fn make_opinionated_ui<'l, WC: WindowComponent<ParentMsg = UIControlMsg>>(
  229. wc: impl 'l + FnOnce(&mut UIHandle) -> WC,
  230. ) -> UI<OpinionatedUIComponent<'l, WC>> {
  231. let ouic = OpinionatedUIComponent {
  232. window: None,
  233. builder: Some(Box::new(wc)),
  234. };
  235. UI::new(ouic, FontStore::new_with_system_fonts())
  236. }