#![warn(missing_docs)]
use crate::component::{ComponentRc, ComponentWeak};
use crate::graphics::{Point, Size};
use crate::input::{KeyEvent, MouseEvent, MouseInputState, TextCursorBlinker};
use crate::items::{ItemRc, ItemRef, ItemWeak, MouseCursor};
use crate::properties::{Property, PropertyTracker};
use alloc::boxed::Box;
use alloc::rc::{Rc, Weak};
use core::cell::{Cell, RefCell};
use core::pin::Pin;
pub trait PlatformWindow {
fn show(self: Rc<Self>);
fn hide(self: Rc<Self>);
fn request_redraw(&self);
fn free_graphics_resources<'a>(&self, items: &mut dyn Iterator<Item = Pin<ItemRef<'a>>>);
fn show_popup(&self, popup: &ComponentRc, position: Point);
fn request_window_properties_update(&self);
fn apply_window_properties(&self, window_item: Pin<&crate::items::WindowItem>);
fn apply_geometry_constraint(
&self,
constraints_horizontal: crate::layout::LayoutInfo,
constraints_vertical: crate::layout::LayoutInfo,
);
fn set_mouse_cursor(&self, cursor: MouseCursor);
fn text_size(
&self,
font_request: crate::graphics::FontRequest,
text: &str,
max_width: Option<f32>,
) -> Size;
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&crate::items::TextInput>,
pos: Point,
) -> usize;
fn text_input_position_for_byte_offset(
&self,
text_input: Pin<&crate::items::TextInput>,
byte_offset: usize,
) -> Point;
fn as_any(&self) -> &dyn core::any::Any;
}
struct WindowPropertiesTracker {
window_weak: Weak<Window>,
}
impl crate::properties::PropertyChangeHandler for WindowPropertiesTracker {
fn notify(&self) {
if let Some(platform_window) =
self.window_weak.upgrade().and_then(|window| window.platform_window.get().cloned())
{
platform_window.request_window_properties_update();
};
}
}
struct WindowRedrawTracker {
window_weak: Weak<Window>,
}
impl crate::properties::PropertyChangeHandler for WindowRedrawTracker {
fn notify(&self) {
if let Some(platform_window) =
self.window_weak.upgrade().and_then(|window| window.platform_window.get().cloned())
{
platform_window.request_redraw();
};
}
}
pub enum PopupWindowLocation {
TopLevel(Rc<Window>),
ChildWindow(Point),
}
pub struct PopupWindow {
pub location: PopupWindowLocation,
pub component: ComponentRc,
}
pub struct Window {
platform_window: once_cell::unsync::OnceCell<Rc<dyn PlatformWindow>>,
component: RefCell<ComponentWeak>,
mouse_input_state: Cell<MouseInputState>,
redraw_tracker: once_cell::unsync::OnceCell<Pin<Box<PropertyTracker<WindowRedrawTracker>>>>,
window_properties_tracker:
once_cell::unsync::OnceCell<Pin<Box<PropertyTracker<WindowPropertiesTracker>>>>,
meta_properties_tracker: Pin<Rc<PropertyTracker>>,
focus_item: RefCell<ItemWeak>,
cursor_blinker: RefCell<pin_weak::rc::PinWeak<crate::input::TextCursorBlinker>>,
scale_factor: Pin<Box<Property<f32>>>,
active: Pin<Box<Property<bool>>>,
active_popup: RefCell<Option<PopupWindow>>,
}
impl Drop for Window {
fn drop(&mut self) {
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
existing_blinker.stop();
}
}
}
impl Window {
pub fn new(
platform_window_fn: impl FnOnce(&Weak<Window>) -> Rc<dyn PlatformWindow>,
) -> Rc<Self> {
#![allow(unused_mut)]
let window = Rc::new(Self {
platform_window: Default::default(),
component: Default::default(),
mouse_input_state: Default::default(),
redraw_tracker: Default::default(),
window_properties_tracker: Default::default(),
meta_properties_tracker: Rc::pin(Default::default()),
focus_item: Default::default(),
cursor_blinker: Default::default(),
scale_factor: Box::pin(Property::new(1.)),
active: Box::pin(Property::new(false)),
active_popup: Default::default(),
});
let window_weak = Rc::downgrade(&window);
window.platform_window.set(platform_window_fn(&window_weak)).ok().unwrap();
let mut window_properties_tracker =
PropertyTracker::new_with_change_handler(WindowPropertiesTracker {
window_weak: window_weak.clone(),
});
let mut redraw_tracker =
PropertyTracker::new_with_change_handler(WindowRedrawTracker { window_weak });
#[cfg(sixtyfps_debug_property)]
{
window.scale_factor.debug_name.replace("sixtyfps_corelib::Window::scale_factor".into());
window.active.debug_name.replace("sixtyfps_corelib::Window::active".into());
window.active.debug_name.replace("sixtyfps_corelib::Window::active".into());
window_properties_tracker
.set_debug_name("sixtyfps_corelib::Window::window_properties_tracker".into());
redraw_tracker.set_debug_name("sixtyfps_corelib::Window::redraw_tracker".into());
}
window.window_properties_tracker.set(Box::pin(window_properties_tracker)).ok().unwrap();
window.redraw_tracker.set(Box::pin(redraw_tracker)).ok().unwrap();
window
}
pub fn set_component(&self, component: &ComponentRc) {
self.close_popup();
self.focus_item.replace(Default::default());
self.mouse_input_state.replace(Default::default());
self.component.replace(ComponentRc::downgrade(component));
self.meta_properties_tracker.set_dirty(); self.request_window_properties_update();
self.request_redraw();
}
pub fn component(&self) -> ComponentRc {
self.component.borrow().upgrade().unwrap()
}
pub fn try_component(&self) -> Option<ComponentRc> {
self.component.borrow().upgrade()
}
pub fn process_mouse_input(self: Rc<Self>, mut event: MouseEvent) {
crate::animations::update_animations();
let embedded_popup_component =
self.active_popup.borrow().as_ref().and_then(|popup| match popup.location {
PopupWindowLocation::TopLevel(_) => None,
PopupWindowLocation::ChildWindow(coordinates) => {
Some((popup.component.clone(), coordinates))
}
});
let component = embedded_popup_component
.as_ref()
.and_then(|(popup_component, coordinates)| {
event.translate(-coordinates.to_vector());
if let MouseEvent::MousePressed { pos, .. } = &event {
let geom = ComponentRc::borrow_pin(popup_component)
.as_ref()
.get_item_ref(0)
.as_ref()
.geometry();
if !geom.contains(*pos) {
self.close_popup();
return None;
}
}
Some(popup_component.clone())
})
.or_else(|| self.component.borrow().upgrade());
let component = if let Some(component) = component {
component
} else {
return;
};
self.mouse_input_state.set(crate::input::process_mouse_input(
component,
event,
&self.clone(),
self.mouse_input_state.take(),
));
if embedded_popup_component.is_some() {
if matches!(event, MouseEvent::MouseReleased { .. }) {
self.close_popup();
}
}
}
pub fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
let mut item = self.focus_item.borrow().clone();
while let Some(focus_item) = item.upgrade() {
if focus_item.borrow().as_ref().key_event(event, &self.clone())
== crate::input::KeyEventResult::EventAccepted
{
return;
}
item = focus_item.parent_item();
}
}
pub fn set_cursor_blink_binding(&self, prop: &crate::Property<bool>) {
let existing_blinker = self.cursor_blinker.borrow().clone();
let blinker = existing_blinker.upgrade().unwrap_or_else(|| {
let new_blinker = TextCursorBlinker::new();
*self.cursor_blinker.borrow_mut() =
pin_weak::rc::PinWeak::downgrade(new_blinker.clone());
new_blinker
});
TextCursorBlinker::set_binding(blinker, prop);
}
pub fn set_focus_item(self: Rc<Self>, focus_item: &ItemRc) {
if let Some(old_focus_item) = self.as_ref().focus_item.borrow().upgrade() {
old_focus_item
.borrow()
.as_ref()
.focus_event(&crate::input::FocusEvent::FocusOut, &self);
}
*self.as_ref().focus_item.borrow_mut() = focus_item.downgrade();
focus_item.borrow().as_ref().focus_event(&crate::input::FocusEvent::FocusIn, &self);
}
pub fn set_focus(self: Rc<Self>, have_focus: bool) {
let event = if have_focus {
crate::input::FocusEvent::WindowReceivedFocus
} else {
crate::input::FocusEvent::WindowLostFocus
};
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
focus_item.borrow().as_ref().focus_event(&event, &self);
}
}
pub fn set_active(&self, active: bool) {
self.active.as_ref().set(active);
}
pub fn active(&self) -> bool {
self.active.as_ref().get()
}
pub fn update_window_properties(&self) {
if let Some(window_properties_tracker) = self.window_properties_tracker.get() {
window_properties_tracker.as_ref().evaluate_as_dependency_root(|| {
let component = self.component();
let component = ComponentRc::borrow_pin(&component);
let root_item = component.as_ref().get_item_ref(0);
if let Some(window_item) =
ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
{
self.platform_window.get().unwrap().apply_window_properties(window_item);
}
});
}
}
pub fn draw_contents(self: Rc<Self>, render_components: impl FnOnce(&[(&ComponentRc, Point)])) {
let draw_fn = || {
let component_rc = self.component();
let component = ComponentRc::borrow_pin(&component_rc);
self.meta_properties_tracker.as_ref().evaluate_if_dirty(|| {
self.apply_geometry_constraint(
component.as_ref().layout_info(crate::layout::Orientation::Horizontal),
component.as_ref().layout_info(crate::layout::Orientation::Vertical),
);
});
let popup_component =
self.active_popup.borrow().as_ref().and_then(|popup| match popup.location {
PopupWindowLocation::TopLevel(_) => None,
PopupWindowLocation::ChildWindow(coordinates) => {
Some((popup.component.clone(), coordinates))
}
});
if let Some((popup_component, popup_coordinates)) = popup_component {
render_components(&[
(&component_rc, Point::default()),
(&popup_component, popup_coordinates),
])
} else {
render_components(&[(&component_rc, Point::default())]);
}
};
if let Some(redraw_tracker) = self.redraw_tracker.get() {
redraw_tracker.as_ref().evaluate_as_dependency_root(draw_fn)
} else {
draw_fn()
}
}
pub fn show(&self) {
self.platform_window.get().unwrap().clone().show();
self.update_window_properties();
}
pub fn hide(&self) {
self.platform_window.get().unwrap().clone().hide();
}
pub fn set_active_popup(&self, popup: PopupWindow) -> Size {
if matches!(popup.location, PopupWindowLocation::ChildWindow(..)) {
self.meta_properties_tracker.set_dirty();
}
let popup_component = ComponentRc::borrow_pin(&popup.component);
let popup_root = popup_component.as_ref().get_item_ref(0);
let (mut w, mut h) = if let Some(window_item) =
ItemRef::downcast_pin::<crate::items::WindowItem>(popup_root)
{
(window_item.width(), window_item.height())
} else {
(0., 0.)
};
let layout_info_h =
popup_component.as_ref().layout_info(crate::layout::Orientation::Horizontal);
let layout_info_v =
popup_component.as_ref().layout_info(crate::layout::Orientation::Vertical);
if w <= 0. {
w = layout_info_h.preferred;
}
if h <= 0. {
h = layout_info_v.preferred;
}
w = w.clamp(layout_info_h.min, layout_info_h.max);
h = h.clamp(layout_info_v.min, layout_info_v.max);
let size = Size::new(w, h);
if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
let width_property =
crate::items::WindowItem::FIELD_OFFSETS.width.apply_pin(window_item);
let height_property =
crate::items::WindowItem::FIELD_OFFSETS.height.apply_pin(window_item);
width_property.set(size.width);
height_property.set(size.height);
};
self.active_popup.replace(Some(popup));
size
}
pub fn show_popup(&self, popup: &ComponentRc, mut position: Point, parent_item: &ItemRc) {
let mut parent_item = parent_item.clone();
loop {
position += parent_item.borrow().as_ref().geometry().origin.to_vector();
parent_item = match parent_item.parent_item().upgrade() {
None => break,
Some(pi) => pi,
}
}
self.platform_window.get().unwrap().show_popup(popup, position)
}
pub fn close_popup(&self) {
if let Some(current_popup) = self.active_popup.replace(None) {
if matches!(current_popup.location, PopupWindowLocation::ChildWindow(..)) {
self.request_redraw();
}
}
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor_property().get()
}
pub fn scale_factor_property(&self) -> Pin<&Property<f32>> {
self.scale_factor.as_ref()
}
pub fn set_scale_factor(&self, factor: f32) {
self.scale_factor.as_ref().set(factor)
}
pub fn default_font_properties(&self) -> crate::graphics::FontRequest {
self.try_component()
.and_then(|component_rc| {
let component = ComponentRc::borrow_pin(&component_rc);
let root_item = component.as_ref().get_item_ref(0);
ItemRef::downcast_pin(root_item).map(
|window_item: Pin<&crate::items::WindowItem>| {
window_item.default_font_properties()
},
)
})
.unwrap_or_default()
}
pub fn set_window_item_geometry(&self, width: f32, height: f32) {
if let Some(component_rc) = self.try_component() {
let component = ComponentRc::borrow_pin(&component_rc);
let root_item = component.as_ref().get_item_ref(0);
if let Some(window_item) = ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
{
window_item.width.set(width);
window_item.height.set(height);
}
}
}
}
impl core::ops::Deref for Window {
type Target = dyn PlatformWindow;
fn deref(&self) -> &Self::Target {
self.platform_window.get().unwrap().as_ref()
}
}
pub trait WindowHandleAccess {
fn window_handle(&self) -> &Rc<Window>;
}
pub type WindowRc = Rc<Window>;
pub mod api {
#[repr(transparent)]
pub struct Window(pub(super) super::WindowRc);
#[doc(hidden)]
impl From<super::WindowRc> for Window {
fn from(window: super::WindowRc) -> Self {
Self(window)
}
}
impl Window {
pub fn show(&self) {
self.0.show();
}
pub fn hide(&self) {
self.0.hide();
}
}
}
impl WindowHandleAccess for api::Window {
fn window_handle(&self) -> &Rc<Window> {
&self.0
}
}
#[cfg(feature = "ffi")]
pub mod ffi {
#![allow(unsafe_code)]
use super::*;
use crate::slice::Slice;
#[allow(non_camel_case_types)]
type c_void = ();
#[repr(C)]
pub struct WindowRcOpaque(*const c_void);
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_drop(handle: *mut WindowRcOpaque) {
assert_eq!(core::mem::size_of::<WindowRc>(), core::mem::size_of::<WindowRcOpaque>());
core::ptr::read(handle as *mut WindowRc);
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_clone(
source: *const WindowRcOpaque,
target: *mut WindowRcOpaque,
) {
assert_eq!(core::mem::size_of::<WindowRc>(), core::mem::size_of::<WindowRcOpaque>());
let window = &*(source as *const WindowRc);
core::ptr::write(target as *mut WindowRc, window.clone());
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_show(handle: *const WindowRcOpaque) {
let window = &*(handle as *const WindowRc);
window.show();
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_hide(handle: *const WindowRcOpaque) {
let window = &*(handle as *const WindowRc);
window.hide();
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_get_scale_factor(
handle: *const WindowRcOpaque,
) -> f32 {
assert_eq!(core::mem::size_of::<WindowRc>(), core::mem::size_of::<WindowRcOpaque>());
let window = &*(handle as *const WindowRc);
window.scale_factor()
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_set_scale_factor(
handle: *const WindowRcOpaque,
value: f32,
) {
let window = &*(handle as *const WindowRc);
window.set_scale_factor(value)
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_free_graphics_resources<'a>(
handle: *const WindowRcOpaque,
items: &Slice<'a, Pin<ItemRef<'a>>>,
) {
let window = &*(handle as *const WindowRc);
window.free_graphics_resources(&mut items.iter().cloned())
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_set_focus_item(
handle: *const WindowRcOpaque,
focus_item: &ItemRc,
) {
let window = &*(handle as *const WindowRc);
window.clone().set_focus_item(focus_item)
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_set_component(
handle: *const WindowRcOpaque,
component: &ComponentRc,
) {
let window = &*(handle as *const WindowRc);
window.set_component(component)
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_windowrc_show_popup(
handle: *const WindowRcOpaque,
popup: &ComponentRc,
position: crate::graphics::Point,
parent_item: &ItemRc,
) {
let window = &*(handle as *const WindowRc);
window.show_popup(popup, position, parent_item);
}
pub unsafe extern "C" fn sixtyfps_windowrc_close_popup(handle: *const WindowRcOpaque) {
let window = &*(handle as *const WindowRc);
window.close_popup();
}
}