mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Move the GraphicsWindow to the graphics backend
This commit is contained in:
parent
41910f6694
commit
ab08502c77
6 changed files with 620 additions and 589 deletions
|
@ -27,13 +27,13 @@ thread_local! {
|
|||
static ALL_WINDOWS: RefCell<std::collections::HashMap<winit::window::WindowId, Weak<dyn GenericWindow>>> = RefCell::new(std::collections::HashMap::new());
|
||||
}
|
||||
|
||||
pub(crate) fn register_window(id: winit::window::WindowId, window: Rc<dyn GenericWindow>) {
|
||||
pub fn register_window(id: winit::window::WindowId, window: Rc<dyn GenericWindow>) {
|
||||
ALL_WINDOWS.with(|windows| {
|
||||
windows.borrow_mut().insert(id, Rc::downgrade(&window));
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_window(id: winit::window::WindowId) {
|
||||
pub fn unregister_window(id: winit::window::WindowId) {
|
||||
ALL_WINDOWS.with(|windows| {
|
||||
windows.borrow_mut().remove(&id);
|
||||
})
|
||||
|
|
|
@ -20,24 +20,15 @@ LICENSE END */
|
|||
created by the backend in a type-erased manner.
|
||||
*/
|
||||
extern crate alloc;
|
||||
use crate::component::{ComponentRc, ComponentWeak};
|
||||
use crate::input::{KeyEvent, KeyboardModifiers, MouseEvent, MouseEventType};
|
||||
use crate::item_rendering::CachedRenderingData;
|
||||
use crate::items::{ItemRc, ItemRef, ItemWeak};
|
||||
use crate::properties::{InterpolatedPropertyValue, Property, PropertyTracker};
|
||||
use crate::properties::InterpolatedPropertyValue;
|
||||
#[cfg(feature = "rtti")]
|
||||
use crate::rtti::{BuiltinItem, FieldInfo, PropertyInfo, ValueType};
|
||||
use crate::slice::Slice;
|
||||
use crate::window::{ComponentWindow, GenericWindow};
|
||||
#[cfg(feature = "rtti")]
|
||||
use crate::Callback;
|
||||
use crate::SharedString;
|
||||
|
||||
use crate::window::ComponentWindow;
|
||||
use crate::{Callback, SharedString};
|
||||
use auto_enums::auto_enum;
|
||||
use const_field_offset::FieldOffsets;
|
||||
use core::pin::Pin;
|
||||
use sixtyfps_corelib_macros::*;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 2D Rectangle
|
||||
|
@ -336,517 +327,6 @@ pub trait GraphicsBackend: Sized {
|
|||
fn window(&self) -> &winit::window::Window;
|
||||
}
|
||||
|
||||
type WindowFactoryFn<Backend> =
|
||||
dyn Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend;
|
||||
|
||||
struct MappedWindow<Backend: GraphicsBackend + 'static> {
|
||||
backend: RefCell<Backend>,
|
||||
constraints: Cell<crate::layout::LayoutInfo>,
|
||||
}
|
||||
|
||||
enum GraphicsWindowBackendState<Backend: GraphicsBackend + 'static> {
|
||||
Unmapped,
|
||||
Mapped(MappedWindow<Backend>),
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend + 'static> GraphicsWindowBackendState<Backend> {
|
||||
fn as_mapped(&self) -> &MappedWindow<Backend> {
|
||||
match self {
|
||||
GraphicsWindowBackendState::Unmapped => panic!(
|
||||
"internal error: tried to access window functions that require a mapped window"
|
||||
),
|
||||
GraphicsWindowBackendState::Mapped(mw) => &mw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FieldOffsets)]
|
||||
#[repr(C)]
|
||||
#[pin]
|
||||
struct WindowProperties {
|
||||
scale_factor: Property<f32>,
|
||||
width: Property<f32>,
|
||||
height: Property<f32>,
|
||||
}
|
||||
|
||||
impl Default for WindowProperties {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scale_factor: Property::new(1.0),
|
||||
width: Property::new(800.),
|
||||
height: Property::new(600.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GraphicsWindow is an implementation of the [GenericWindow][`crate::eventloop::GenericWindow`] trait. This is
|
||||
/// typically instantiated by entry factory functions of the different graphics backends.
|
||||
pub struct GraphicsWindow<Backend: GraphicsBackend + 'static> {
|
||||
window_factory: Box<WindowFactoryFn<Backend>>,
|
||||
map_state: RefCell<GraphicsWindowBackendState<Backend>>,
|
||||
properties: Pin<Box<WindowProperties>>,
|
||||
cursor_blinker: std::cell::RefCell<pin_weak::rc::PinWeak<TextCursorBlinker>>,
|
||||
keyboard_modifiers: std::cell::Cell<KeyboardModifiers>,
|
||||
component: std::cell::RefCell<ComponentWeak>,
|
||||
/// Gets dirty when the layout restrictions, or some other property of the windows change
|
||||
meta_property_listener: Pin<Rc<PropertyTracker>>,
|
||||
focus_item: std::cell::RefCell<ItemWeak>,
|
||||
mouse_input_state: std::cell::Cell<crate::input::MouseInputState>,
|
||||
/// Current popup's component and position
|
||||
/// FIXME: the popup should actually be another window, not just some overlay
|
||||
active_popup: std::cell::RefCell<Option<(ComponentRc, Point)>>,
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend + 'static> GraphicsWindow<Backend> {
|
||||
/// Creates a new reference-counted instance.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `graphics_backend_factory`: The factor function stored in the GraphicsWindow that's called when the state
|
||||
/// of the window changes to mapped. The event loop and window builder parameters can be used to create a
|
||||
/// backing window.
|
||||
pub fn new(
|
||||
graphics_backend_factory: impl Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend
|
||||
+ 'static,
|
||||
) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
window_factory: Box::new(graphics_backend_factory),
|
||||
map_state: RefCell::new(GraphicsWindowBackendState::Unmapped),
|
||||
properties: Box::pin(WindowProperties::default()),
|
||||
cursor_blinker: Default::default(),
|
||||
keyboard_modifiers: Default::default(),
|
||||
component: Default::default(),
|
||||
meta_property_listener: Rc::pin(Default::default()),
|
||||
focus_item: Default::default(),
|
||||
mouse_input_state: Default::default(),
|
||||
active_popup: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the window id of the window if it is mapped, None otherwise.
|
||||
pub fn id(&self) -> Option<winit::window::WindowId> {
|
||||
Some(self.map_state.borrow().as_mapped().backend.borrow().window().id())
|
||||
}
|
||||
|
||||
fn apply_geometry_constraint(&self, constraints: crate::layout::LayoutInfo) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
if constraints != window.constraints.get() {
|
||||
let min_width = constraints.min_width.min(constraints.max_width);
|
||||
let min_height = constraints.min_height.min(constraints.max_height);
|
||||
let max_width = constraints.max_width.max(constraints.min_width);
|
||||
let max_height = constraints.max_height.max(constraints.min_height);
|
||||
|
||||
window.backend.borrow().window().set_min_inner_size(
|
||||
if min_width > 0. || min_height > 0. {
|
||||
Some(winit::dpi::PhysicalSize::new(min_width, min_height))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
window.backend.borrow().window().set_max_inner_size(
|
||||
if max_width < f32::MAX || max_height < f32::MAX {
|
||||
Some(winit::dpi::PhysicalSize::new(
|
||||
max_width.min(65535.),
|
||||
max_height.min(65535.),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
window.constraints.set(constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn apply_window_properties(&self, window_item: Pin<&crate::items::Window>) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
let backend = window.backend.borrow();
|
||||
backend.window().set_title(
|
||||
crate::items::Window::FIELD_OFFSETS.title.apply_pin(window_item).get().as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests for the window to be mapped to the screen.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `event_loop`: The event loop used to drive further event handling for this window
|
||||
/// as it will receive events.
|
||||
/// * `component`: The component that holds the root item of the scene. If the item is a [`crate::items::Window`], then
|
||||
/// the `width` and `height` properties are read and the values are passed to the windowing system as request
|
||||
/// for the initial size of the window. Then bindings are installed on these properties to keep them up-to-date
|
||||
/// with the size as it may be changed by the user or the windowing system in general.
|
||||
fn map_window(self: Rc<Self>, event_loop: &crate::eventloop::EventLoop) {
|
||||
if matches!(&*self.map_state.borrow(), GraphicsWindowBackendState::Mapped(..)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let component = self.component.borrow().upgrade().unwrap();
|
||||
let component = ComponentRc::borrow_pin(&component);
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
|
||||
let window_title = if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
crate::items::Window::FIELD_OFFSETS.title.apply_pin(window_item).get().to_string()
|
||||
} else {
|
||||
"SixtyFPS Window".to_string()
|
||||
};
|
||||
let window_builder = winit::window::WindowBuilder::new().with_title(window_title);
|
||||
|
||||
let id = {
|
||||
let backend = self.window_factory.as_ref()(&event_loop, window_builder);
|
||||
|
||||
let platform_window = backend.window();
|
||||
|
||||
if std::env::var("SIXTYFPS_FULLSCREEN").is_ok() {
|
||||
platform_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
|
||||
}
|
||||
|
||||
let window_id = platform_window.id();
|
||||
|
||||
// Ideally we should be passing the initial requested size to the window builder, but those properties
|
||||
// may be specified in logical pixels, relative to the scale factory, which we only know *after* mapping
|
||||
// the window to the screen. So we first map the window then, propagate the scale factory and *then* the
|
||||
// width/height properties should have the correct values calculated via their bindings that multiply with
|
||||
// the scale factor.
|
||||
// We could pass the logical requested size at window builder time, *if* we knew what the values are.
|
||||
{
|
||||
self.properties.as_ref().scale_factor.set(platform_window.scale_factor() as _);
|
||||
let existing_size = platform_window.inner_size();
|
||||
|
||||
let mut new_size = existing_size;
|
||||
|
||||
if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
let width =
|
||||
crate::items::Window::FIELD_OFFSETS.width.apply_pin(window_item).get();
|
||||
if width > 0. {
|
||||
new_size.width = width as _;
|
||||
}
|
||||
let height =
|
||||
crate::items::Window::FIELD_OFFSETS.height.apply_pin(window_item).get();
|
||||
if height > 0. {
|
||||
new_size.height = height as _;
|
||||
}
|
||||
|
||||
{
|
||||
let window = self.clone();
|
||||
window_item.as_ref().width.set_binding(move || {
|
||||
WindowProperties::FIELD_OFFSETS
|
||||
.width
|
||||
.apply_pin(window.properties.as_ref())
|
||||
.get()
|
||||
});
|
||||
}
|
||||
{
|
||||
let window = self.clone();
|
||||
window_item.as_ref().height.set_binding(move || {
|
||||
WindowProperties::FIELD_OFFSETS
|
||||
.height
|
||||
.apply_pin(window.properties.as_ref())
|
||||
.get()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if new_size != existing_size {
|
||||
platform_window.set_inner_size(new_size)
|
||||
}
|
||||
|
||||
self.properties.as_ref().width.set(new_size.width as _);
|
||||
self.properties.as_ref().height.set(new_size.height as _);
|
||||
}
|
||||
|
||||
self.map_state.replace(GraphicsWindowBackendState::Mapped(MappedWindow {
|
||||
backend: RefCell::new(backend),
|
||||
constraints: Default::default(),
|
||||
}));
|
||||
|
||||
window_id
|
||||
};
|
||||
|
||||
crate::eventloop::register_window(id, self.clone() as Rc<dyn GenericWindow>);
|
||||
}
|
||||
/// Removes the window from the screen. The window is not destroyed though, it can be show (mapped) again later
|
||||
/// by calling [`GenericWindow::map_window`].
|
||||
fn unmap_window(self: Rc<Self>) {
|
||||
self.map_state.replace(GraphicsWindowBackendState::Unmapped);
|
||||
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
||||
existing_blinker.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend> Drop for GraphicsWindow<Backend> {
|
||||
fn drop(&mut self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(mw) => {
|
||||
crate::eventloop::unregister_window(mw.backend.borrow().window().id());
|
||||
}
|
||||
}
|
||||
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
||||
existing_blinker.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend> GenericWindow for GraphicsWindow<Backend> {
|
||||
fn set_component(self: Rc<Self>, component: &ComponentRc) {
|
||||
*self.component.borrow_mut() = vtable::VRc::downgrade(&component)
|
||||
}
|
||||
|
||||
fn draw(self: Rc<Self>) {
|
||||
let component_rc = self.component.borrow().upgrade().unwrap();
|
||||
let component = ComponentRc::borrow_pin(&component_rc);
|
||||
|
||||
{
|
||||
if self.meta_property_listener.as_ref().is_dirty() {
|
||||
self.meta_property_listener.as_ref().evaluate(|| {
|
||||
self.apply_geometry_constraint(component.as_ref().layout_info());
|
||||
component.as_ref().apply_layout(self.get_geometry());
|
||||
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
self.apply_window_properties(window_item);
|
||||
}
|
||||
|
||||
if let Some((popup, pos)) = &*self.active_popup.borrow() {
|
||||
let popup = ComponentRc::borrow_pin(popup);
|
||||
let popup_root = popup.as_ref().get_item_ref(0);
|
||||
let size = if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
|
||||
let layout_info = popup.as_ref().layout_info();
|
||||
|
||||
let width =
|
||||
crate::items::Window::FIELD_OFFSETS.width.apply_pin(window_item);
|
||||
let mut w = width.get();
|
||||
if w < layout_info.min_width {
|
||||
w = layout_info.min_width;
|
||||
width.set(w);
|
||||
}
|
||||
|
||||
let height =
|
||||
crate::items::Window::FIELD_OFFSETS.height.apply_pin(window_item);
|
||||
let mut h = height.get();
|
||||
if h < layout_info.min_height {
|
||||
h = layout_info.min_height;
|
||||
height.set(h);
|
||||
}
|
||||
Size::new(h, w)
|
||||
} else {
|
||||
Size::default()
|
||||
};
|
||||
popup.as_ref().apply_layout(Rect::new(pos.clone(), size));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let map_state = self.map_state.borrow();
|
||||
let window = map_state.as_mapped();
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
let background_color = if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
crate::items::Window::FIELD_OFFSETS.color.apply_pin(window_item).get()
|
||||
} else {
|
||||
RgbaColor { red: 255 as u8, green: 255, blue: 255, alpha: 255 }.into()
|
||||
};
|
||||
|
||||
let mut renderer = window.backend.borrow_mut().new_renderer(&background_color);
|
||||
crate::item_rendering::render_component_items::<Backend>(
|
||||
&component_rc,
|
||||
&mut renderer,
|
||||
Point::default(),
|
||||
);
|
||||
if let Some(popup) = &*self.active_popup.borrow() {
|
||||
crate::item_rendering::render_component_items::<Backend>(
|
||||
&popup.0,
|
||||
&mut renderer,
|
||||
popup.1,
|
||||
);
|
||||
}
|
||||
window.backend.borrow_mut().flush_renderer(renderer);
|
||||
}
|
||||
|
||||
fn process_mouse_input(self: Rc<Self>, mut pos: Point, what: MouseEventType) {
|
||||
let active_popup = (*self.active_popup.borrow()).clone();
|
||||
let component = if let Some(popup) = &active_popup {
|
||||
pos -= popup.1.to_vector();
|
||||
if what == MouseEventType::MousePressed {
|
||||
// close the popup if one press outside the popup
|
||||
let geom =
|
||||
ComponentRc::borrow_pin(&popup.0).as_ref().get_item_ref(0).as_ref().geometry();
|
||||
if !geom.contains(pos) {
|
||||
self.close_popup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
popup.0.clone()
|
||||
} else {
|
||||
self.component.borrow().upgrade().unwrap()
|
||||
};
|
||||
|
||||
self.mouse_input_state.set(crate::input::process_mouse_input(
|
||||
component,
|
||||
MouseEvent { pos, what },
|
||||
&ComponentWindow::new(self.clone()),
|
||||
self.mouse_input_state.take(),
|
||||
));
|
||||
|
||||
if active_popup.is_some() {
|
||||
//FIXME: currently the ComboBox is the only thing that uses the popup, and it should close automatically
|
||||
// on release. But ideally, there would be API to close the popup rather than always closing it on release
|
||||
if what == MouseEventType::MouseReleased {
|
||||
self.close_popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
|
||||
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
|
||||
let window = &ComponentWindow::new(self.clone());
|
||||
focus_item.borrow().as_ref().key_event(event, &window);
|
||||
}
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
window.backend.borrow().window().request_redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
WindowProperties::FIELD_OFFSETS.scale_factor.apply_pin(self.properties.as_ref()).get()
|
||||
}
|
||||
|
||||
fn set_scale_factor(&self, factor: f32) {
|
||||
self.properties.as_ref().scale_factor.set(factor);
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
window.backend.borrow_mut().refresh_window_scale_factor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_window_scale_factor(&self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
let sf = window.backend.borrow().window().scale_factor();
|
||||
self.set_scale_factor(sf as f32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_width(&self, width: f32) {
|
||||
self.properties.as_ref().width.set(width);
|
||||
}
|
||||
|
||||
fn set_height(&self, height: f32) {
|
||||
self.properties.as_ref().height.set(height);
|
||||
}
|
||||
|
||||
fn get_geometry(&self) -> crate::graphics::Rect {
|
||||
euclid::rect(
|
||||
0.,
|
||||
0.,
|
||||
WindowProperties::FIELD_OFFSETS.width.apply_pin(self.properties.as_ref()).get(),
|
||||
WindowProperties::FIELD_OFFSETS.height.apply_pin(self.properties.as_ref()).get(),
|
||||
)
|
||||
}
|
||||
|
||||
fn free_graphics_resources<'a>(self: Rc<Self>, items: &Slice<'a, Pin<ItemRef<'a>>>) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
crate::item_rendering::free_item_rendering_data(items, &window.backend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cursor_blink_binding(&self, prop: &crate::properties::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);
|
||||
}
|
||||
|
||||
/// Returns the currently active keyboard notifiers.
|
||||
fn current_keyboard_modifiers(&self) -> KeyboardModifiers {
|
||||
self.keyboard_modifiers.get()
|
||||
}
|
||||
/// Sets the currently active keyboard notifiers. This is used only for testing or directly
|
||||
/// from the event loop implementation.
|
||||
fn set_current_keyboard_modifiers(&self, state: KeyboardModifiers) {
|
||||
self.keyboard_modifiers.set(state)
|
||||
}
|
||||
|
||||
fn set_focus_item(self: Rc<Self>, focus_item: &ItemRc) {
|
||||
let window = ComponentWindow::new(self.clone());
|
||||
|
||||
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, &window);
|
||||
}
|
||||
|
||||
*self.as_ref().focus_item.borrow_mut() = focus_item.downgrade();
|
||||
|
||||
focus_item.borrow().as_ref().focus_event(&crate::input::FocusEvent::FocusIn, &window);
|
||||
}
|
||||
|
||||
fn set_focus(self: Rc<Self>, have_focus: bool) {
|
||||
let window = ComponentWindow::new(self.clone());
|
||||
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, &window);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_popup(&self, popup: &ComponentRc, position: Point) {
|
||||
self.meta_property_listener.set_dirty();
|
||||
*self.active_popup.borrow_mut() = Some((popup.clone(), position));
|
||||
}
|
||||
|
||||
fn close_popup(&self) {
|
||||
*self.active_popup.borrow_mut() = None;
|
||||
}
|
||||
|
||||
fn run(self: Rc<Self>) {
|
||||
let event_loop = crate::eventloop::EventLoop::new();
|
||||
|
||||
self.clone().map_window(&event_loop);
|
||||
event_loop.run();
|
||||
self.unmap_window();
|
||||
}
|
||||
|
||||
fn font(&self, request: crate::graphics::FontRequest) -> Option<Rc<dyn crate::graphics::Font>> {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => None,
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
Some(window.backend.borrow_mut().font(request))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(FieldOffsets, Default, BuiltinItem, Clone, Debug, PartialEq)]
|
||||
#[pin]
|
||||
|
@ -1211,66 +691,6 @@ pub(crate) mod ffi {
|
|||
}
|
||||
}
|
||||
|
||||
/// The TextCursorBlinker takes care of providing a toggled boolean property
|
||||
/// that can be used to animate a blinking cursor. It's typically stored in the
|
||||
/// Window using a Weak and set_binding() can be used to set up a binding on a given
|
||||
/// property that'll keep it up-to-date. That binding keeps a strong reference to the
|
||||
/// blinker. If the underlying item that uses it goes away, the binding goes away and
|
||||
/// so does the blinker.
|
||||
#[derive(FieldOffsets)]
|
||||
#[repr(C)]
|
||||
#[pin]
|
||||
struct TextCursorBlinker {
|
||||
cursor_visible: Property<bool>,
|
||||
cursor_blink_timer: crate::timers::Timer,
|
||||
}
|
||||
|
||||
impl TextCursorBlinker {
|
||||
fn new() -> Pin<Rc<Self>> {
|
||||
Rc::pin(Self {
|
||||
cursor_visible: Property::new(true),
|
||||
cursor_blink_timer: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_binding(instance: Pin<Rc<TextCursorBlinker>>, prop: &crate::properties::Property<bool>) {
|
||||
instance.as_ref().cursor_visible.set(true);
|
||||
// Re-start timer, in case.
|
||||
Self::start(&instance);
|
||||
prop.set_binding(move || {
|
||||
TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
|
||||
});
|
||||
}
|
||||
|
||||
fn start(self: &Pin<Rc<Self>>) {
|
||||
if self.cursor_blink_timer.running() {
|
||||
self.cursor_blink_timer.restart();
|
||||
} else {
|
||||
let toggle_cursor = {
|
||||
let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
|
||||
move || {
|
||||
if let Some(blinker) = weak_blinker.upgrade() {
|
||||
let visible = TextCursorBlinker::FIELD_OFFSETS
|
||||
.cursor_visible
|
||||
.apply_pin(blinker.as_ref())
|
||||
.get();
|
||||
blinker.cursor_visible.set(!visible);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.cursor_blink_timer.start(
|
||||
crate::timers::TimerMode::Repeated,
|
||||
std::time::Duration::from_millis(500),
|
||||
toggle_cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
self.cursor_blink_timer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
/// This function can be used to register a custom TrueType font with SixtyFPS,
|
||||
/// for use with the `font-family` property. The provided slice must be a valid TrueType
|
||||
/// font.
|
||||
|
|
|
@ -58,7 +58,7 @@ impl CachedRenderingData {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render_component_items<Backend: GraphicsBackend>(
|
||||
pub fn render_component_items<Backend: GraphicsBackend>(
|
||||
component: &ComponentRc,
|
||||
renderer: &mut Backend::ItemRenderer,
|
||||
origin: crate::graphics::Point,
|
||||
|
@ -86,7 +86,7 @@ pub(crate) fn render_component_items<Backend: GraphicsBackend>(
|
|||
);
|
||||
}
|
||||
|
||||
pub(crate) fn free_item_rendering_data<'a, Backend: GraphicsBackend>(
|
||||
pub fn free_item_rendering_data<'a, Backend: GraphicsBackend>(
|
||||
items: &Slice<'a, core::pin::Pin<ItemRef<'a>>>,
|
||||
renderer: &RefCell<Backend>,
|
||||
) {
|
||||
|
|
|
@ -19,6 +19,7 @@ default = ["x11"]
|
|||
|
||||
[dependencies]
|
||||
sixtyfps-corelib = { version = "=0.0.4", path = "../../corelib", features = ["femtovg_backend"] }
|
||||
const-field-offset = { version = "0.1", path = "../../../helper_crates/const-field-offset" }
|
||||
lyon = { version = "0.16" }
|
||||
image = { version = "0.23.12", default-features = false }
|
||||
rgb = "0.8"
|
||||
|
@ -32,6 +33,7 @@ smallvec = "1.4.1"
|
|||
by_address = "1.0.4"
|
||||
femtovg = { git = "https://github.com/femtovg/femtovg", branch = "master" }
|
||||
euclid = "0.22.1"
|
||||
pin-weak = "1"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web_sys = { version = "0.3", package = "web-sys", features=["console", "WebGlContextAttributes"] }
|
||||
|
|
607
sixtyfps_runtime/rendering_backends/gl/graphics_window.rs
Normal file
607
sixtyfps_runtime/rendering_backends/gl/graphics_window.rs
Normal file
|
@ -0,0 +1,607 @@
|
|||
/* LICENSE BEGIN
|
||||
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
This file is also available under commercial licensing terms.
|
||||
Please contact info@sixtyfps.io for more information.
|
||||
LICENSE END */
|
||||
//! This module contains the GraphicsWindow that used to be within corelib.
|
||||
//! FIXME The GraphicsWindow probably does not need to be generic
|
||||
|
||||
use core::cell::{Cell, RefCell};
|
||||
use core::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
|
||||
use const_field_offset::FieldOffsets;
|
||||
use corelib::component::{ComponentRc, ComponentWeak};
|
||||
use corelib::graphics::*;
|
||||
use corelib::input::{KeyEvent, KeyboardModifiers, MouseEvent, MouseEventType};
|
||||
use corelib::items::{ItemRc, ItemRef, ItemWeak};
|
||||
use corelib::properties::PropertyTracker;
|
||||
use corelib::slice::Slice;
|
||||
use corelib::window::{ComponentWindow, GenericWindow};
|
||||
use corelib::Property;
|
||||
use sixtyfps_corelib as corelib;
|
||||
|
||||
type WindowFactoryFn<Backend> =
|
||||
dyn Fn(&corelib::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend;
|
||||
|
||||
/// GraphicsWindow is an implementation of the [GenericWindow][`corelib::eventloop::GenericWindow`] trait. This is
|
||||
/// typically instantiated by entry factory functions of the different graphics backends.
|
||||
pub struct GraphicsWindow<Backend: GraphicsBackend + 'static> {
|
||||
window_factory: Box<WindowFactoryFn<Backend>>,
|
||||
map_state: RefCell<GraphicsWindowBackendState<Backend>>,
|
||||
properties: Pin<Box<WindowProperties>>,
|
||||
cursor_blinker: RefCell<pin_weak::rc::PinWeak<TextCursorBlinker>>,
|
||||
keyboard_modifiers: std::cell::Cell<KeyboardModifiers>,
|
||||
component: std::cell::RefCell<ComponentWeak>,
|
||||
/// Gets dirty when the layout restrictions, or some other property of the windows change
|
||||
meta_property_listener: Pin<Rc<PropertyTracker>>,
|
||||
focus_item: std::cell::RefCell<ItemWeak>,
|
||||
mouse_input_state: std::cell::Cell<corelib::input::MouseInputState>,
|
||||
/// Current popup's component and position
|
||||
/// FIXME: the popup should actually be another window, not just some overlay
|
||||
active_popup: std::cell::RefCell<Option<(ComponentRc, Point)>>,
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend + 'static> GraphicsWindow<Backend> {
|
||||
/// Creates a new reference-counted instance.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `graphics_backend_factory`: The factor function stored in the GraphicsWindow that's called when the state
|
||||
/// of the window changes to mapped. The event loop and window builder parameters can be used to create a
|
||||
/// backing window.
|
||||
pub fn new(
|
||||
graphics_backend_factory: impl Fn(&corelib::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend
|
||||
+ 'static,
|
||||
) -> Rc<Self> {
|
||||
Rc::new(Self {
|
||||
window_factory: Box::new(graphics_backend_factory),
|
||||
map_state: RefCell::new(GraphicsWindowBackendState::Unmapped),
|
||||
properties: Box::pin(WindowProperties::default()),
|
||||
cursor_blinker: Default::default(),
|
||||
keyboard_modifiers: Default::default(),
|
||||
component: Default::default(),
|
||||
meta_property_listener: Rc::pin(Default::default()),
|
||||
focus_item: Default::default(),
|
||||
mouse_input_state: Default::default(),
|
||||
active_popup: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the window id of the window if it is mapped, None otherwise.
|
||||
pub fn id(&self) -> Option<winit::window::WindowId> {
|
||||
Some(self.map_state.borrow().as_mapped().backend.borrow().window().id())
|
||||
}
|
||||
|
||||
fn apply_geometry_constraint(&self, constraints: corelib::layout::LayoutInfo) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
if constraints != window.constraints.get() {
|
||||
let min_width = constraints.min_width.min(constraints.max_width);
|
||||
let min_height = constraints.min_height.min(constraints.max_height);
|
||||
let max_width = constraints.max_width.max(constraints.min_width);
|
||||
let max_height = constraints.max_height.max(constraints.min_height);
|
||||
|
||||
window.backend.borrow().window().set_min_inner_size(
|
||||
if min_width > 0. || min_height > 0. {
|
||||
Some(winit::dpi::PhysicalSize::new(min_width, min_height))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
window.backend.borrow().window().set_max_inner_size(
|
||||
if max_width < f32::MAX || max_height < f32::MAX {
|
||||
Some(winit::dpi::PhysicalSize::new(
|
||||
max_width.min(65535.),
|
||||
max_height.min(65535.),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
);
|
||||
window.constraints.set(constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn apply_window_properties(&self, window_item: Pin<&corelib::items::Window>) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
let backend = window.backend.borrow();
|
||||
backend.window().set_title(
|
||||
corelib::items::Window::FIELD_OFFSETS
|
||||
.title
|
||||
.apply_pin(window_item)
|
||||
.get()
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests for the window to be mapped to the screen.
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `event_loop`: The event loop used to drive further event handling for this window
|
||||
/// as it will receive events.
|
||||
/// * `component`: The component that holds the root item of the scene. If the item is a [`corelib::items::Window`], then
|
||||
/// the `width` and `height` properties are read and the values are passed to the windowing system as request
|
||||
/// for the initial size of the window. Then bindings are installed on these properties to keep them up-to-date
|
||||
/// with the size as it may be changed by the user or the windowing system in general.
|
||||
fn map_window(self: Rc<Self>, event_loop: &corelib::eventloop::EventLoop) {
|
||||
if matches!(&*self.map_state.borrow(), GraphicsWindowBackendState::Mapped(..)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let component = self.component.borrow().upgrade().unwrap();
|
||||
let component = ComponentRc::borrow_pin(&component);
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
|
||||
let window_title = if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
corelib::items::Window::FIELD_OFFSETS.title.apply_pin(window_item).get().to_string()
|
||||
} else {
|
||||
"SixtyFPS Window".to_string()
|
||||
};
|
||||
let window_builder = winit::window::WindowBuilder::new().with_title(window_title);
|
||||
|
||||
let id = {
|
||||
let backend = self.window_factory.as_ref()(&event_loop, window_builder);
|
||||
|
||||
let platform_window = backend.window();
|
||||
|
||||
if std::env::var("SIXTYFPS_FULLSCREEN").is_ok() {
|
||||
platform_window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
|
||||
}
|
||||
|
||||
let window_id = platform_window.id();
|
||||
|
||||
// Ideally we should be passing the initial requested size to the window builder, but those properties
|
||||
// may be specified in logical pixels, relative to the scale factory, which we only know *after* mapping
|
||||
// the window to the screen. So we first map the window then, propagate the scale factory and *then* the
|
||||
// width/height properties should have the correct values calculated via their bindings that multiply with
|
||||
// the scale factor.
|
||||
// We could pass the logical requested size at window builder time, *if* we knew what the values are.
|
||||
{
|
||||
self.properties.as_ref().scale_factor.set(platform_window.scale_factor() as _);
|
||||
let existing_size = platform_window.inner_size();
|
||||
|
||||
let mut new_size = existing_size;
|
||||
|
||||
if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
let width =
|
||||
corelib::items::Window::FIELD_OFFSETS.width.apply_pin(window_item).get();
|
||||
if width > 0. {
|
||||
new_size.width = width as _;
|
||||
}
|
||||
let height =
|
||||
corelib::items::Window::FIELD_OFFSETS.height.apply_pin(window_item).get();
|
||||
if height > 0. {
|
||||
new_size.height = height as _;
|
||||
}
|
||||
|
||||
{
|
||||
let window = self.clone();
|
||||
window_item.as_ref().width.set_binding(move || {
|
||||
WindowProperties::FIELD_OFFSETS
|
||||
.width
|
||||
.apply_pin(window.properties.as_ref())
|
||||
.get()
|
||||
});
|
||||
}
|
||||
{
|
||||
let window = self.clone();
|
||||
window_item.as_ref().height.set_binding(move || {
|
||||
WindowProperties::FIELD_OFFSETS
|
||||
.height
|
||||
.apply_pin(window.properties.as_ref())
|
||||
.get()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if new_size != existing_size {
|
||||
platform_window.set_inner_size(new_size)
|
||||
}
|
||||
|
||||
self.properties.as_ref().width.set(new_size.width as _);
|
||||
self.properties.as_ref().height.set(new_size.height as _);
|
||||
}
|
||||
|
||||
self.map_state.replace(GraphicsWindowBackendState::Mapped(MappedWindow {
|
||||
backend: RefCell::new(backend),
|
||||
constraints: Default::default(),
|
||||
}));
|
||||
|
||||
window_id
|
||||
};
|
||||
|
||||
corelib::eventloop::register_window(id, self.clone() as Rc<dyn GenericWindow>);
|
||||
}
|
||||
/// Removes the window from the screen. The window is not destroyed though, it can be show (mapped) again later
|
||||
/// by calling [`GenericWindow::map_window`].
|
||||
fn unmap_window(self: Rc<Self>) {
|
||||
self.map_state.replace(GraphicsWindowBackendState::Unmapped);
|
||||
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
||||
existing_blinker.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend> Drop for GraphicsWindow<Backend> {
|
||||
fn drop(&mut self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(mw) => {
|
||||
corelib::eventloop::unregister_window(mw.backend.borrow().window().id());
|
||||
}
|
||||
}
|
||||
if let Some(existing_blinker) = self.cursor_blinker.borrow().upgrade() {
|
||||
existing_blinker.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend> GenericWindow for GraphicsWindow<Backend> {
|
||||
fn set_component(self: Rc<Self>, component: &ComponentRc) {
|
||||
*self.component.borrow_mut() = vtable::VRc::downgrade(&component)
|
||||
}
|
||||
|
||||
fn draw(self: Rc<Self>) {
|
||||
let component_rc = self.component.borrow().upgrade().unwrap();
|
||||
let component = ComponentRc::borrow_pin(&component_rc);
|
||||
|
||||
{
|
||||
if self.meta_property_listener.as_ref().is_dirty() {
|
||||
self.meta_property_listener.as_ref().evaluate(|| {
|
||||
self.apply_geometry_constraint(component.as_ref().layout_info());
|
||||
component.as_ref().apply_layout(self.get_geometry());
|
||||
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
self.apply_window_properties(window_item);
|
||||
}
|
||||
|
||||
if let Some((popup, pos)) = &*self.active_popup.borrow() {
|
||||
let popup = ComponentRc::borrow_pin(popup);
|
||||
let popup_root = popup.as_ref().get_item_ref(0);
|
||||
let size = if let Some(window_item) = ItemRef::downcast_pin(popup_root) {
|
||||
let layout_info = popup.as_ref().layout_info();
|
||||
|
||||
let width =
|
||||
corelib::items::Window::FIELD_OFFSETS.width.apply_pin(window_item);
|
||||
let mut w = width.get();
|
||||
if w < layout_info.min_width {
|
||||
w = layout_info.min_width;
|
||||
width.set(w);
|
||||
}
|
||||
|
||||
let height =
|
||||
corelib::items::Window::FIELD_OFFSETS.height.apply_pin(window_item);
|
||||
let mut h = height.get();
|
||||
if h < layout_info.min_height {
|
||||
h = layout_info.min_height;
|
||||
height.set(h);
|
||||
}
|
||||
Size::new(h, w)
|
||||
} else {
|
||||
Size::default()
|
||||
};
|
||||
popup.as_ref().apply_layout(Rect::new(pos.clone(), size));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let map_state = self.map_state.borrow();
|
||||
let window = map_state.as_mapped();
|
||||
let root_item = component.as_ref().get_item_ref(0);
|
||||
let background_color = if let Some(window_item) = ItemRef::downcast_pin(root_item) {
|
||||
corelib::items::Window::FIELD_OFFSETS.color.apply_pin(window_item).get()
|
||||
} else {
|
||||
RgbaColor { red: 255 as u8, green: 255, blue: 255, alpha: 255 }.into()
|
||||
};
|
||||
|
||||
let mut renderer = window.backend.borrow_mut().new_renderer(&background_color);
|
||||
corelib::item_rendering::render_component_items::<Backend>(
|
||||
&component_rc,
|
||||
&mut renderer,
|
||||
Point::default(),
|
||||
);
|
||||
if let Some(popup) = &*self.active_popup.borrow() {
|
||||
corelib::item_rendering::render_component_items::<Backend>(
|
||||
&popup.0,
|
||||
&mut renderer,
|
||||
popup.1,
|
||||
);
|
||||
}
|
||||
window.backend.borrow_mut().flush_renderer(renderer);
|
||||
}
|
||||
|
||||
fn process_mouse_input(self: Rc<Self>, mut pos: Point, what: MouseEventType) {
|
||||
let active_popup = (*self.active_popup.borrow()).clone();
|
||||
let component = if let Some(popup) = &active_popup {
|
||||
pos -= popup.1.to_vector();
|
||||
if what == MouseEventType::MousePressed {
|
||||
// close the popup if one press outside the popup
|
||||
let geom =
|
||||
ComponentRc::borrow_pin(&popup.0).as_ref().get_item_ref(0).as_ref().geometry();
|
||||
if !geom.contains(pos) {
|
||||
self.close_popup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
popup.0.clone()
|
||||
} else {
|
||||
self.component.borrow().upgrade().unwrap()
|
||||
};
|
||||
|
||||
self.mouse_input_state.set(corelib::input::process_mouse_input(
|
||||
component,
|
||||
MouseEvent { pos, what },
|
||||
&ComponentWindow::new(self.clone()),
|
||||
self.mouse_input_state.take(),
|
||||
));
|
||||
|
||||
if active_popup.is_some() {
|
||||
//FIXME: currently the ComboBox is the only thing that uses the popup, and it should close automatically
|
||||
// on release. But ideally, there would be API to close the popup rather than always closing it on release
|
||||
if what == MouseEventType::MouseReleased {
|
||||
self.close_popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_key_input(self: Rc<Self>, event: &KeyEvent) {
|
||||
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
|
||||
let window = &ComponentWindow::new(self.clone());
|
||||
focus_item.borrow().as_ref().key_event(event, &window);
|
||||
}
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
window.backend.borrow().window().request_redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
WindowProperties::FIELD_OFFSETS.scale_factor.apply_pin(self.properties.as_ref()).get()
|
||||
}
|
||||
|
||||
fn set_scale_factor(&self, factor: f32) {
|
||||
self.properties.as_ref().scale_factor.set(factor);
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
window.backend.borrow_mut().refresh_window_scale_factor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_window_scale_factor(&self) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
let sf = window.backend.borrow().window().scale_factor();
|
||||
self.set_scale_factor(sf as f32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_width(&self, width: f32) {
|
||||
self.properties.as_ref().width.set(width);
|
||||
}
|
||||
|
||||
fn set_height(&self, height: f32) {
|
||||
self.properties.as_ref().height.set(height);
|
||||
}
|
||||
|
||||
fn get_geometry(&self) -> corelib::graphics::Rect {
|
||||
euclid::rect(
|
||||
0.,
|
||||
0.,
|
||||
WindowProperties::FIELD_OFFSETS.width.apply_pin(self.properties.as_ref()).get(),
|
||||
WindowProperties::FIELD_OFFSETS.height.apply_pin(self.properties.as_ref()).get(),
|
||||
)
|
||||
}
|
||||
|
||||
fn free_graphics_resources<'a>(self: Rc<Self>, items: &Slice<'a, Pin<ItemRef<'a>>>) {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => {}
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
corelib::item_rendering::free_item_rendering_data(items, &window.backend)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cursor_blink_binding(&self, prop: &corelib::properties::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);
|
||||
}
|
||||
|
||||
/// Returns the currently active keyboard notifiers.
|
||||
fn current_keyboard_modifiers(&self) -> KeyboardModifiers {
|
||||
self.keyboard_modifiers.get()
|
||||
}
|
||||
/// Sets the currently active keyboard notifiers. This is used only for testing or directly
|
||||
/// from the event loop implementation.
|
||||
fn set_current_keyboard_modifiers(&self, state: KeyboardModifiers) {
|
||||
self.keyboard_modifiers.set(state)
|
||||
}
|
||||
|
||||
fn set_focus_item(self: Rc<Self>, focus_item: &ItemRc) {
|
||||
let window = ComponentWindow::new(self.clone());
|
||||
|
||||
if let Some(old_focus_item) = self.as_ref().focus_item.borrow().upgrade() {
|
||||
old_focus_item
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.focus_event(&corelib::input::FocusEvent::FocusOut, &window);
|
||||
}
|
||||
|
||||
*self.as_ref().focus_item.borrow_mut() = focus_item.downgrade();
|
||||
|
||||
focus_item.borrow().as_ref().focus_event(&corelib::input::FocusEvent::FocusIn, &window);
|
||||
}
|
||||
|
||||
fn set_focus(self: Rc<Self>, have_focus: bool) {
|
||||
let window = ComponentWindow::new(self.clone());
|
||||
let event = if have_focus {
|
||||
corelib::input::FocusEvent::WindowReceivedFocus
|
||||
} else {
|
||||
corelib::input::FocusEvent::WindowLostFocus
|
||||
};
|
||||
|
||||
if let Some(focus_item) = self.as_ref().focus_item.borrow().upgrade() {
|
||||
focus_item.borrow().as_ref().focus_event(&event, &window);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_popup(&self, popup: &ComponentRc, position: Point) {
|
||||
self.meta_property_listener.set_dirty();
|
||||
*self.active_popup.borrow_mut() = Some((popup.clone(), position));
|
||||
}
|
||||
|
||||
fn close_popup(&self) {
|
||||
*self.active_popup.borrow_mut() = None;
|
||||
}
|
||||
|
||||
fn run(self: Rc<Self>) {
|
||||
let event_loop = corelib::eventloop::EventLoop::new();
|
||||
|
||||
self.clone().map_window(&event_loop);
|
||||
event_loop.run();
|
||||
self.unmap_window();
|
||||
}
|
||||
|
||||
fn font(
|
||||
&self,
|
||||
request: corelib::graphics::FontRequest,
|
||||
) -> Option<Rc<dyn corelib::graphics::Font>> {
|
||||
match &*self.map_state.borrow() {
|
||||
GraphicsWindowBackendState::Unmapped => None,
|
||||
GraphicsWindowBackendState::Mapped(window) => {
|
||||
Some(window.backend.borrow_mut().font(request))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MappedWindow<Backend: GraphicsBackend + 'static> {
|
||||
backend: RefCell<Backend>,
|
||||
constraints: Cell<corelib::layout::LayoutInfo>,
|
||||
}
|
||||
|
||||
enum GraphicsWindowBackendState<Backend: GraphicsBackend + 'static> {
|
||||
Unmapped,
|
||||
Mapped(MappedWindow<Backend>),
|
||||
}
|
||||
|
||||
impl<Backend: GraphicsBackend + 'static> GraphicsWindowBackendState<Backend> {
|
||||
fn as_mapped(&self) -> &MappedWindow<Backend> {
|
||||
match self {
|
||||
GraphicsWindowBackendState::Unmapped => panic!(
|
||||
"internal error: tried to access window functions that require a mapped window"
|
||||
),
|
||||
GraphicsWindowBackendState::Mapped(mw) => &mw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FieldOffsets)]
|
||||
#[repr(C)]
|
||||
#[pin]
|
||||
struct WindowProperties {
|
||||
scale_factor: Property<f32>,
|
||||
width: Property<f32>,
|
||||
height: Property<f32>,
|
||||
}
|
||||
|
||||
impl Default for WindowProperties {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scale_factor: Property::new(1.0),
|
||||
width: Property::new(800.),
|
||||
height: Property::new(600.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The TextCursorBlinker takes care of providing a toggled boolean property
|
||||
/// that can be used to animate a blinking cursor. It's typically stored in the
|
||||
/// Window using a Weak and set_binding() can be used to set up a binding on a given
|
||||
/// property that'll keep it up-to-date. That binding keeps a strong reference to the
|
||||
/// blinker. If the underlying item that uses it goes away, the binding goes away and
|
||||
/// so does the blinker.
|
||||
#[derive(FieldOffsets)]
|
||||
#[repr(C)]
|
||||
#[pin]
|
||||
struct TextCursorBlinker {
|
||||
cursor_visible: Property<bool>,
|
||||
cursor_blink_timer: corelib::timers::Timer,
|
||||
}
|
||||
|
||||
impl TextCursorBlinker {
|
||||
fn new() -> Pin<Rc<Self>> {
|
||||
Rc::pin(Self {
|
||||
cursor_visible: Property::new(true),
|
||||
cursor_blink_timer: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_binding(
|
||||
instance: Pin<Rc<TextCursorBlinker>>,
|
||||
prop: &corelib::properties::Property<bool>,
|
||||
) {
|
||||
instance.as_ref().cursor_visible.set(true);
|
||||
// Re-start timer, in case.
|
||||
Self::start(&instance);
|
||||
prop.set_binding(move || {
|
||||
TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
|
||||
});
|
||||
}
|
||||
|
||||
fn start(self: &Pin<Rc<Self>>) {
|
||||
if self.cursor_blink_timer.running() {
|
||||
self.cursor_blink_timer.restart();
|
||||
} else {
|
||||
let toggle_cursor = {
|
||||
let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
|
||||
move || {
|
||||
if let Some(blinker) = weak_blinker.upgrade() {
|
||||
let visible = TextCursorBlinker::FIELD_OFFSETS
|
||||
.cursor_visible
|
||||
.apply_pin(blinker.as_ref())
|
||||
.get();
|
||||
blinker.cursor_visible.set(!visible);
|
||||
}
|
||||
}
|
||||
};
|
||||
self.cursor_blink_timer.start(
|
||||
corelib::timers::TimerMode::Repeated,
|
||||
std::time::Duration::from_millis(500),
|
||||
toggle_cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
self.cursor_blink_timer.stop()
|
||||
}
|
||||
}
|
|
@ -11,14 +11,16 @@ LICENSE END */
|
|||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use sixtyfps_corelib::graphics::{
|
||||
Color, Font, FontRequest, GraphicsBackend, GraphicsWindow, Point, Rect, RenderingCache,
|
||||
Resource,
|
||||
Color, Font, FontRequest, GraphicsBackend, Point, Rect, RenderingCache, Resource,
|
||||
};
|
||||
use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer};
|
||||
use sixtyfps_corelib::items::Item;
|
||||
use sixtyfps_corelib::window::ComponentWindow;
|
||||
use sixtyfps_corelib::{Property, SharedString, SharedVector};
|
||||
|
||||
mod graphics_window;
|
||||
use graphics_window::*;
|
||||
|
||||
type CanvasRc = Rc<RefCell<femtovg::Canvas<femtovg::renderer::OpenGl>>>;
|
||||
type RenderingCacheRc = Rc<RefCell<RenderingCache<Option<GPUCachedData>>>>;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue