123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- use std::{collections::HashMap, rc::Rc};
- use kahlo::math::{PixelPoint, PixelSize};
- use crate::{
- component::Component,
- font::FontStore,
- input::MouseButton,
- layout::{LayoutCache, LayoutNode},
- theme::Theme,
- window::{Window, WindowBuilder, WindowComponent, WindowEvent, WindowStateAccess},
- };
- #[derive(Debug)]
- pub enum UIControlMsg {
- Terminate,
- }
- pub trait UIComponent: Sized + Component<ParentMsg = UIControlMsg> {
- fn init(&mut self, ui_handle: &mut UIHandle);
- fn poll(&mut self, ui_handle: &mut UIHandle) -> Vec<UIControlMsg>;
- }
- pub(crate) struct UIState {
- pub(crate) layout_cache: std::rc::Rc<LayoutCache>,
- pub(crate) window_states:
- HashMap<winit::window::WindowId, std::rc::Weak<dyn WindowStateAccess>>,
- pub(crate) fontstore: FontStore,
- pub(crate) theme: Theme,
- }
- pub struct UIHandle<'l> {
- pub(crate) state: &'l mut UIState,
- pub(crate) eloop: &'l winit::event_loop::ActiveEventLoop,
- }
- impl<'l> UIHandle<'l> {
- pub fn window_builder<'r>(&'r mut self) -> WindowBuilder<'r, 'l> {
- WindowBuilder::new(self)
- }
- pub fn new_layout_node(&self) -> LayoutNode {
- LayoutNode::new(self.state.layout_cache.clone())
- }
- pub fn theme(&self) -> &Theme {
- &self.state.theme
- }
- pub fn lookup_font(&self, q: &fontdb::Query) -> Rc<fontdue::Font> {
- self.state
- .fontstore
- .query_for_font(q)
- .expect("no matching font found")
- }
- }
- pub struct UI<UIC: UIComponent> {
- state: UIState,
- event_loop: Option<winit::event_loop::EventLoop<()>>,
- ui_component: UIC,
- autoclose: bool,
- initialized: bool,
- }
- impl<UIC: UIComponent> UI<UIC> {
- pub fn new(uic: UIC, fontstore: FontStore) -> Self {
- let lcache = LayoutCache::new();
- let eloop = winit::event_loop::EventLoop::builder().build().unwrap();
- eloop.set_control_flow(winit::event_loop::ControlFlow::Wait);
- Self {
- state: UIState {
- layout_cache: lcache,
- window_states: Default::default(),
- theme: Theme::build_default(&fontstore),
- fontstore,
- },
- event_loop: Some(eloop),
- ui_component: uic,
- autoclose: true,
- initialized: false,
- }
- }
- pub fn disable_autoclose(mut self) -> Self {
- self.autoclose = false;
- self
- }
- pub fn ui_component(&self) -> &UIC {
- &self.ui_component
- }
- pub fn ui_component_mut(&mut self) -> &mut UIC {
- &mut self.ui_component
- }
- /// Public helper function, delivers a message to the UI component and then processes whatever
- /// control messages result.
- pub fn deliver_msg(&mut self, msg: UIC::Msg) {
- let cmsgs = self.ui_component.process(msg);
- self.process_control_msgs(cmsgs.into_iter());
- }
- fn process_control_msgs(&mut self, msgs: impl Iterator<Item = UIControlMsg>) {
- for cmsg in msgs {
- match cmsg {
- UIControlMsg::Terminate => {
- todo!()
- // self.ui_running = false;
- }
- }
- }
- }
- pub fn run(mut self) {
- let eloop = self.event_loop.take().unwrap();
- eloop.run_app(&mut self).unwrap();
- }
- fn pump_events(&mut self, eloop: &winit::event_loop::ActiveEventLoop) {
- let evts = self.ui_component.poll(&mut UIHandle {
- eloop,
- state: &mut self.state,
- });
- for evt in evts.into_iter() {
- match evt {
- UIControlMsg::Terminate => eloop.exit(),
- }
- }
- }
- }
- impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
- fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
- if !self.initialized {
- self.ui_component.init(&mut UIHandle {
- eloop: event_loop,
- state: &mut self.state,
- });
- self.initialized = true;
- }
- }
- fn window_event(
- &mut self,
- event_loop: &winit::event_loop::ActiveEventLoop,
- window_id: winit::window::WindowId,
- event: winit::event::WindowEvent,
- ) {
- let Some(wsa) = self
- .state
- .window_states
- .get(&window_id)
- .and_then(std::rc::Weak::upgrade)
- else {
- println!("superfluous event: {event:?}");
- return;
- };
- match event {
- winit::event::WindowEvent::CloseRequested => {
- wsa.push_event(WindowEvent::CloseRequested);
- self.pump_events(event_loop);
- }
- winit::event::WindowEvent::Destroyed => {
- self.state.window_states.remove(&window_id);
- }
- winit::event::WindowEvent::RedrawRequested => {
- wsa.redraw(&UIHandle {
- eloop: event_loop,
- state: &mut self.state,
- });
- self.pump_events(event_loop);
- }
- winit::event::WindowEvent::CursorMoved { position, .. } => {
- wsa.update_mouse_pos(PixelPoint::new(position.x as i32, position.y as i32));
- wsa.request_redraw();
- self.pump_events(event_loop);
- }
- winit::event::WindowEvent::MouseInput { state, button, .. } => {
- let button = match button {
- winit::event::MouseButton::Left => MouseButton::Left,
- winit::event::MouseButton::Right => MouseButton::Right,
- _ => return,
- };
- wsa.update_mouse_button(button, state.is_pressed());
- wsa.request_redraw();
- self.pump_events(event_loop);
- }
- winit::event::WindowEvent::Resized(newsize) => {
- wsa.notify_resize(PixelSize::new(newsize.width as i32, newsize.height as i32));
- wsa.request_redraw();
- }
- winit::event::WindowEvent::KeyboardInput {
- device_id,
- event,
- is_synthetic,
- } => {
- if event.state == winit::event::ElementState::Pressed {
- if let Some(text) = event.text {
- wsa.update_text_input(text.as_str());
- }
- wsa.push_keypress(event.logical_key);
- self.pump_events(event_loop);
- wsa.request_redraw();
- }
- }
- _ => {}
- }
- }
- }
- /// Opinionated base UI component.
- ///
- /// Most of the time, this will be the UI component you will want to use: a single main window,
- /// passing UI events back from the window component. Unless you want multiple windows, or need to
- /// use a custom font directory, there is no reason to not use this component.
- pub struct OpinionatedUIComponent<'l, WC: WindowComponent> {
- window: Option<Window<WC>>,
- builder: Option<Box<dyn FnOnce(&mut UIHandle) -> WC + 'l>>,
- }
- impl<'l, WC: WindowComponent> OpinionatedUIComponent<'l, WC> {}
- impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> Component
- for OpinionatedUIComponent<'l, WC>
- {
- type ParentMsg = UIControlMsg;
- type Msg = UIControlMsg;
- fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
- vec![msg]
- }
- }
- impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> UIComponent
- for OpinionatedUIComponent<'l, WC>
- {
- fn init(&mut self, ui_handle: &mut UIHandle) {
- if let Some(builder) = self.builder.take() {
- self.window = Some(ui_handle.window_builder().build(builder));
- }
- }
- fn poll(&mut self, uih: &mut UIHandle) -> Vec<UIControlMsg> {
- if let Some(window) = self.window.as_mut() {
- window.poll(uih)
- } else {
- vec![]
- }
- }
- }
- /// Build an [`OpinionatedUIComponent`] for a given [`WindowComponent`] that passes
- /// [`UIControlMsg`]s back to its parent component and uses system fonts.
- pub fn make_opinionated_ui<'l, WC: WindowComponent<ParentMsg = UIControlMsg>>(
- wc: impl 'l + FnOnce(&mut UIHandle) -> WC,
- ) -> UI<OpinionatedUIComponent<'l, WC>> {
- let ouic = OpinionatedUIComponent {
- window: None,
- builder: Some(Box::new(wc)),
- };
- UI::new(ouic, FontStore::new_with_system_fonts())
- }
|