use log::{debug, info, trace};
use std::time::Instant;
use kas::event::{CursorIcon, ManagerState, UpdateHandle};
use kas::geom::{Coord, Rect, Size};
use kas::layout::SolveCache;
use kas::string::{CowString, CowStringL};
use kas::{ThemeAction, ThemeApi, TkAction, WindowId};
use kas_theme::Theme;
use winit::dpi::PhysicalSize;
use winit::error::OsError;
use winit::event::WindowEvent;
use winit::event_loop::EventLoopWindowTarget;
use winit::window::WindowBuilder;
use crate::draw::{CustomPipe, CustomWindow, DrawPipe, DrawWindow, TEX_FORMAT};
use crate::shared::{PendingAction, SharedState};
use crate::ProxyAction;
pub(crate) struct Window<CW: CustomWindow, TW> {
pub(crate) widget: Box<dyn kas::Window>,
pub(crate) window_id: WindowId,
mgr: ManagerState,
solve_cache: SolveCache,
pub(crate) window: winit::window::Window,
surface: wgpu::Surface,
sc_desc: wgpu::SwapChainDescriptor,
swap_chain: wgpu::SwapChain,
draw: DrawWindow<CW>,
theme_window: TW,
}
impl<CW, TW> Window<CW, TW>
where
CW: CustomWindow + 'static,
TW: kas_theme::Window<DrawWindow<CW>> + 'static,
{
pub fn new<C, T>(
shared: &mut SharedState<C, T>,
elwt: &EventLoopWindowTarget<ProxyAction>,
window_id: WindowId,
mut widget: Box<dyn kas::Window>,
) -> Result<Self, OsError>
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let scale_factor = shared.scale_factor as f32;
let mut draw = shared.draw.new_window(&mut shared.device, Size::ZERO);
let mut theme_window = shared.theme.new_window(&mut draw, scale_factor);
let mut size_handle = unsafe { theme_window.size_handle(&mut draw) };
let solve_cache = SolveCache::find_constraints(widget.as_widget_mut(), &mut size_handle);
let ideal = solve_cache.ideal(true);
drop(size_handle);
let mut builder = WindowBuilder::new().with_inner_size(ideal);
let restrict_dimensions = widget.restrict_dimensions();
if restrict_dimensions.0 {
builder = builder.with_min_inner_size(solve_cache.min(true));
}
if restrict_dimensions.1 {
builder = builder.with_max_inner_size(ideal);
}
let window = builder.with_title(widget.title()).build(elwt)?;
let scale_factor = window.scale_factor();
shared.scale_factor = scale_factor;
let size: Size = window.inner_size().into();
info!("Constucted new window with size {:?}", size);
let buf = shared.draw.resize(&mut draw, &shared.device, size);
shared.queue.submit(&[buf]);
let surface = wgpu::Surface::create(&window);
let sc_desc = wgpu::SwapChainDescriptor {
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
format: TEX_FORMAT,
width: size.0,
height: size.1,
present_mode: wgpu::PresentMode::Fifo,
};
let swap_chain = shared.device.create_swap_chain(&surface, &sc_desc);
let mut mgr = ManagerState::new(scale_factor);
let mut tkw = TkWindow::new(&window, shared);
mgr.configure(&mut tkw, &mut *widget);
let mut r = Window {
widget,
window_id,
mgr,
solve_cache,
window,
surface,
sc_desc,
swap_chain,
draw,
theme_window,
};
r.apply_size();
Ok(r)
}
fn reconfigure<C, T>(&mut self, shared: &mut SharedState<C, T>)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
debug!("Window::reconfigure");
let mut tkw = TkWindow::new(&self.window, shared);
self.mgr.configure(&mut tkw, &mut *self.widget);
self.solve_cache.invalidate_rule_cache();
self.apply_size();
}
pub fn theme_resize<C, T>(&mut self, shared: &SharedState<C, T>)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
debug!("Window::theme_resize");
let scale_factor = self.window.scale_factor() as f32;
shared
.theme
.update_window(&mut self.theme_window, scale_factor);
self.solve_cache.invalidate_rule_cache();
self.apply_size();
}
pub fn handle_event<C, T>(&mut self, shared: &mut SharedState<C, T>, event: WindowEvent)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
match event {
WindowEvent::Destroyed => (),
WindowEvent::Resized(size) => self.do_resize(shared, size),
WindowEvent::ScaleFactorChanged {
scale_factor,
new_inner_size,
} => {
shared.scale_factor = scale_factor;
shared
.theme
.update_window(&mut self.theme_window, scale_factor as f32);
self.mgr.set_dpi_factor(scale_factor);
self.solve_cache.invalidate_rule_cache();
self.do_resize(shared, *new_inner_size);
}
event @ _ => {
let mut tkw = TkWindow::new(&self.window, shared);
let widget = &mut *self.widget;
self.mgr.with(&mut tkw, |mgr| {
mgr.handle_winit(widget, event);
});
}
}
}
pub fn update<C, T>(&mut self, shared: &mut SharedState<C, T>) -> (TkAction, Option<Instant>)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let mut tkw = TkWindow::new(&self.window, shared);
let action = self.mgr.update(&mut tkw, &mut *self.widget);
match action {
TkAction::None => (),
TkAction::Redraw => self.window.request_redraw(),
TkAction::RegionMoved => {
self.mgr.region_moved(&mut tkw, &mut *self.widget);
self.window.request_redraw();
}
TkAction::Popup => {
let mut size_handle = unsafe { self.theme_window.size_handle(&mut self.draw) };
self.widget.resize_popups(&mut size_handle);
self.mgr.region_moved(&mut tkw, &mut *self.widget);
self.window.request_redraw();
}
TkAction::Reconfigure => self.reconfigure(shared),
TkAction::Close | TkAction::CloseAll => (),
}
(action, self.mgr.next_resume())
}
pub fn handle_closure<C, T>(mut self, shared: &mut SharedState<C, T>) -> TkAction
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let mut tkw = TkWindow::new(&self.window, shared);
let widget = &mut *self.widget;
self.mgr.with(&mut tkw, |mut mgr| {
widget.handle_closure(&mut mgr);
});
self.mgr.update(&mut tkw, &mut *self.widget)
}
pub fn update_timer<C, T>(&mut self, shared: &mut SharedState<C, T>) -> Option<Instant>
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let mut tkw = TkWindow::new(&self.window, shared);
let widget = &mut *self.widget;
self.mgr.with(&mut tkw, |mgr| {
mgr.update_timer(widget);
});
self.mgr.next_resume()
}
pub fn update_handle<C, T>(
&mut self,
shared: &mut SharedState<C, T>,
handle: UpdateHandle,
payload: u64,
) where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let mut tkw = TkWindow::new(&self.window, shared);
let widget = &mut *self.widget;
self.mgr.with(&mut tkw, |mgr| {
mgr.update_handle(widget, handle, payload);
});
}
pub fn add_popup<C, T>(
&mut self,
shared: &mut SharedState<C, T>,
id: WindowId,
popup: kas::Popup,
) where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let window = &mut *self.widget;
let mut size_handle = unsafe { self.theme_window.size_handle(&mut self.draw) };
let mut tkw = TkWindow::new(&self.window, shared);
self.mgr.with(&mut tkw, |mut mgr| {
kas::Window::add_popup(window, &mut size_handle, &mut mgr, id, popup);
});
}
pub fn send_action(&mut self, action: TkAction) {
self.mgr.send_action(action);
}
pub fn send_close<C, T>(&mut self, shared: &mut SharedState<C, T>, id: WindowId)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
if id == self.window_id {
self.mgr.send_action(TkAction::Close);
} else {
let mut tkw = TkWindow::new(&self.window, shared);
let widget = &mut *self.widget;
self.mgr.with(&mut tkw, |mut mgr| {
widget.remove_popup(&mut mgr, id);
});
}
}
}
impl<CW, TW> Window<CW, TW>
where
CW: CustomWindow + 'static,
TW: kas_theme::Window<DrawWindow<CW>> + 'static,
{
fn apply_size(&mut self) {
let size = Size(self.sc_desc.width, self.sc_desc.height);
let rect = Rect::new(Coord::ZERO, size);
debug!("Resizing window to rect = {:?}", rect);
let mut size_handle = unsafe { self.theme_window.size_handle(&mut self.draw) };
self.solve_cache
.apply_rect(self.widget.as_widget_mut(), &mut size_handle, rect, true);
self.widget.resize_popups(&mut size_handle);
let restrict_dimensions = self.widget.restrict_dimensions();
if restrict_dimensions.0 {
self.window
.set_min_inner_size(Some(self.solve_cache.min(true)));
};
if restrict_dimensions.1 {
self.window
.set_max_inner_size(Some(self.solve_cache.ideal(true)));
};
self.window.request_redraw();
}
fn do_resize<C, T>(&mut self, shared: &mut SharedState<C, T>, size: PhysicalSize<u32>)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
let size = size.into();
if size == Size(self.sc_desc.width, self.sc_desc.height) {
return;
}
let buf = shared.draw.resize(&mut self.draw, &shared.device, size);
shared.queue.submit(&[buf]);
self.sc_desc.width = size.0;
self.sc_desc.height = size.1;
self.swap_chain = shared
.device
.create_swap_chain(&self.surface, &self.sc_desc);
self.apply_size();
}
pub(crate) fn do_draw<C, T>(&mut self, shared: &mut SharedState<C, T>)
where
C: CustomPipe<Window = CW>,
T: Theme<DrawPipe<C>, Window = TW>,
{
trace!("Window::do_draw");
let size = Size(self.sc_desc.width, self.sc_desc.height);
let rect = Rect {
pos: Coord::ZERO,
size,
};
let mut draw_handle = unsafe {
shared
.theme
.draw_handle(&mut self.draw, &mut self.theme_window, rect)
};
self.widget.draw(&mut draw_handle, &self.mgr, false);
drop(draw_handle);
let frame = self.swap_chain.get_next_texture().unwrap();
let clear_color = to_wgpu_color(shared.theme.clear_colour());
shared.render(&mut self.draw, &frame.view, clear_color);
}
}
fn to_wgpu_color(c: kas::draw::Colour) -> wgpu::Color {
wgpu::Color {
r: c.r as f64,
g: c.g as f64,
b: c.b as f64,
a: c.a as f64,
}
}
struct TkWindow<'a, C: CustomPipe, T> {
window: &'a winit::window::Window,
shared: &'a mut SharedState<C, T>,
}
impl<'a, C: CustomPipe, T> TkWindow<'a, C, T> {
fn new(window: &'a winit::window::Window, shared: &'a mut SharedState<C, T>) -> Self {
TkWindow { window, shared }
}
}
impl<'a, C, T> kas::TkWindow for TkWindow<'a, C, T>
where
C: CustomPipe,
T: Theme<DrawPipe<C>>,
T::Window: kas_theme::Window<DrawWindow<C::Window>>,
{
fn add_popup(&mut self, popup: kas::Popup) -> WindowId {
let id = self.shared.next_window_id();
let parent_id = self.window.id();
self.shared
.pending
.push(PendingAction::AddPopup(parent_id, id, popup));
id
}
fn add_window(&mut self, widget: Box<dyn kas::Window>) -> WindowId {
let id = self.shared.next_window_id();
self.shared
.pending
.push(PendingAction::AddWindow(id, widget));
id
}
fn close_window(&mut self, id: WindowId) {
self.shared.pending.push(PendingAction::CloseWindow(id));
}
fn trigger_update(&mut self, handle: UpdateHandle, payload: u64) {
self.shared
.pending
.push(PendingAction::Update(handle, payload));
}
#[inline]
fn get_clipboard(&mut self) -> Option<CowString> {
self.shared.get_clipboard()
}
#[inline]
fn set_clipboard<'c>(&mut self, content: CowStringL<'c>) {
self.shared.set_clipboard(content);
}
fn adjust_theme(&mut self, f: &mut dyn FnMut(&mut dyn ThemeApi) -> ThemeAction) {
match f(&mut self.shared.theme) {
ThemeAction::None => (),
ThemeAction::RedrawAll => self.shared.pending.push(PendingAction::RedrawAll),
ThemeAction::ThemeResize => self.shared.pending.push(PendingAction::ThemeResize),
}
}
#[inline]
fn set_cursor_icon(&mut self, icon: CursorIcon) {
self.window.set_cursor_icon(icon);
}
}