/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann 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 */ use cpp::*; use items::{ImageFit, TextHorizontalAlignment, TextVerticalAlignment}; use sixtyfps_corelib::graphics::{Color, FontRequest, Point, RenderingCache}; use sixtyfps_corelib::input::{InternalKeyCode, KeyEvent, KeyEventType, MouseEventType}; use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer}; use sixtyfps_corelib::items::{self, ItemRef, TextOverflow, TextWrap}; use sixtyfps_corelib::properties::PropertyTracker; use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::window::PlatformWindow; use sixtyfps_corelib::{component::ComponentRc, SharedString}; use sixtyfps_corelib::{PathData, Property, Resource}; use std::cell::RefCell; use std::pin::Pin; use std::ptr::NonNull; use std::rc::{Rc, Weak}; use crate::key_generated; use super::qttypes; cpp! {{ #include #include #include #include #include #include #include #include #include #include #include void ensure_initialized(); struct TimerHandler : QObject { QBasicTimer timer; static TimerHandler& instance() { static TimerHandler instance; return instance; } void timerEvent(QTimerEvent *event) override { if (event->timerId() != timer.timerId()) { QObject::timerEvent(event); return; } timer.stop(); rust!(SFPS_timerEvent [] { timer_event() }); } }; struct SixtyFPSWidget : QWidget { void *rust_window; void paintEvent(QPaintEvent *) override { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); auto painter_ptr = &painter; rust!(SFPS_paintEvent [rust_window: &QtWindow as "void*", painter_ptr: &mut QPainter as "QPainter*"] { rust_window.paint_event(painter_ptr) }); } void resizeEvent(QResizeEvent *event) override { QSize size = event->size(); rust!(SFPS_resizeEvent [rust_window: &QtWindow as "void*", size: qttypes::QSize as "QSize"] { rust_window.resize_event(size) }); } void mousePressEvent(QMouseEvent *event) override { QPoint pos = event->pos(); rust!(SFPS_mousePressEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] { rust_window.mouse_event(MouseEventType::MousePressed, pos) }); } void mouseReleaseEvent(QMouseEvent *event) override { QPoint pos = event->pos(); rust!(SFPS_mouseReleaseEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] { rust_window.mouse_event(MouseEventType::MouseReleased, pos) }); if (auto p = dynamic_cast(parent())) { // FIXME: better way to close the popup void *parent_window = p->rust_window; rust!(SFPS_mouseReleaseEventPopup [parent_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] { parent_window.close_popup(); }); } } void mouseMoveEvent(QMouseEvent *event) override { QPoint pos = event->pos(); rust!(SFPS_mouseMoveEvent [rust_window: &QtWindow as "void*", pos: qttypes::QPoint as "QPoint"] { rust_window.mouse_event(MouseEventType::MouseMoved, pos) }); } void keyPressEvent(QKeyEvent *event) override { uint modif = uint(event->modifiers()); QString text = event->text(); int key = event->key(); rust!(SFPS_keyPress [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", modif: u32 as "uint"] { rust_window.key_event(key, text.clone(), modif, false); }); } void keyReleaseEvent(QKeyEvent *event) override { uint modif = uint(event->modifiers()); QString text = event->text(); int key = event->key(); rust!(SFPS_keyRelease [rust_window: &QtWindow as "void*", key: i32 as "int", text: qttypes::QString as "QString", modif: u32 as "uint"] { rust_window.key_event(key, text.clone(), modif, true); }); } }; }} cpp_class! {pub unsafe struct QPainter as "QPainter"} impl QPainter { pub fn save_state(&mut self) { cpp! { unsafe [self as "QPainter*"] { self->save(); }} } pub fn restore_state(&mut self) { cpp! { unsafe [self as "QPainter*"] { self->restore(); }} } } cpp_class! {pub unsafe struct QPainterPath as "QPainterPath"} impl QPainterPath { /* pub fn reserve(&mut self, size: usize) { cpp! { unsafe [self as "QPainterPath*", size as "long long"] { self->reserve(size); }} }*/ pub fn move_to(&mut self, to: qttypes::QPointF) { cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] { self->moveTo(to); }} } pub fn line_to(&mut self, to: qttypes::QPointF) { cpp! { unsafe [self as "QPainterPath*", to as "QPointF"] { self->lineTo(to); }} } pub fn quad_to(&mut self, ctrl: qttypes::QPointF, to: qttypes::QPointF) { cpp! { unsafe [self as "QPainterPath*", ctrl as "QPointF", to as "QPointF"] { self->quadTo(ctrl, to); }} } pub fn cubic_to( &mut self, ctrl1: qttypes::QPointF, ctrl2: qttypes::QPointF, to: qttypes::QPointF, ) { cpp! { unsafe [self as "QPainterPath*", ctrl1 as "QPointF", ctrl2 as "QPointF", to as "QPointF"] { self->cubicTo(ctrl1, ctrl2, to); }} } pub fn close(&mut self) { cpp! { unsafe [self as "QPainterPath*"] { self->closeSubpath(); }} } } /// Given a position offset and an object of a given type that has x,y,width,height properties, /// create a QRectF that fits it. macro_rules! get_geometry { ($pos:expr, $ty:ty, $obj:expr) => {{ type Ty = $ty; let width = Ty::FIELD_OFFSETS.width.apply_pin($obj).get(); let height = Ty::FIELD_OFFSETS.height.apply_pin($obj).get(); let x = Ty::FIELD_OFFSETS.x.apply_pin($obj).get(); let y = Ty::FIELD_OFFSETS.y.apply_pin($obj).get(); if width < 1. || height < 1. { return Default::default(); }; qttypes::QRectF { x: (x + $pos.x as f32) as _, y: (y + $pos.y as f32) as _, width: width as _, height: height as _, } }}; } #[derive(Clone)] enum QtRenderingCacheItem { Pixmap(qttypes::QPixmap), Invalid, } type QtRenderingCache = Rc>>; struct QtItemRenderer<'a> { painter: &'a mut QPainter, cache: QtRenderingCache, } impl ItemRenderer for QtItemRenderer<'_> { fn draw_rectangle(&mut self, pos: Point, rect: Pin<&items::Rectangle>) { let pos = qttypes::QPoint { x: pos.x as _, y: pos.y as _ }; let color: u32 = rect.color().as_argb_encoded(); let rect: qttypes::QRectF = get_geometry!(pos, items::Rectangle, rect); let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", color as "QRgb", rect as "QRectF"] { painter->fillRect(rect, QColor::fromRgba(color)); }} } fn draw_border_rectangle(&mut self, pos: Point, rect: std::pin::Pin<&items::BorderRectangle>) { self.draw_rectangle_impl( get_geometry!(pos, items::BorderRectangle, rect), rect.color(), rect.border_color(), rect.border_width(), rect.border_radius(), ); } fn draw_image(&mut self, pos: Point, image: Pin<&items::Image>) { let dest_rect: qttypes::QRectF = get_geometry!(pos, items::Image, image); self.draw_image_impl( &image.cached_rendering_data, items::Image::FIELD_OFFSETS.source.apply_pin(image), dest_rect, None, image.image_fit(), ); } fn draw_clipped_image(&mut self, pos: Point, image: Pin<&items::ClippedImage>) { let dest_rect: qttypes::QRectF = get_geometry!(pos, items::ClippedImage, image); let source_rect = qttypes::QRectF { x: image.source_clip_x() as _, y: image.source_clip_y() as _, width: image.source_clip_width() as _, height: image.source_clip_height() as _, }; self.draw_image_impl( &image.cached_rendering_data, items::ClippedImage::FIELD_OFFSETS.source.apply_pin(image), dest_rect, Some(source_rect), image.image_fit(), ); } fn draw_text(&mut self, pos: Point, text: std::pin::Pin<&items::Text>) { let rect: qttypes::QRectF = get_geometry!(pos, items::Text, text); let color: u32 = text.color().as_argb_encoded(); let string: qttypes::QString = text.text().as_str().into(); let font: QFont = get_font(text.font_request()); let flags = match text.horizontal_alignment() { TextHorizontalAlignment::left => key_generated::Qt_AlignmentFlag_AlignLeft, TextHorizontalAlignment::center => key_generated::Qt_AlignmentFlag_AlignHCenter, TextHorizontalAlignment::right => key_generated::Qt_AlignmentFlag_AlignRight, } | match text.vertical_alignment() { TextVerticalAlignment::top => key_generated::Qt_AlignmentFlag_AlignTop, TextVerticalAlignment::center => key_generated::Qt_AlignmentFlag_AlignVCenter, TextVerticalAlignment::bottom => key_generated::Qt_AlignmentFlag_AlignBottom, } | match text.wrap() { TextWrap::no_wrap => 0, TextWrap::word_wrap => key_generated::Qt_TextFlag_TextWordWrap, }; let elide = text.overflow() == TextOverflow::elide && text.wrap() == TextWrap::no_wrap; let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", rect as "QRectF", color as "QRgb", string as "QString", flags as "int", font as "QFont", elide as "bool"] { painter->setFont(font); painter->setPen(QColor{color}); painter->setBrush(Qt::NoBrush); if (!elide) { painter->drawText(rect, flags, string); } else { auto elided = QFontMetrics(font).elidedText(string, Qt::ElideRight, rect.width()); painter->drawText(rect, flags, elided); } }} } fn draw_text_input(&mut self, pos: Point, text_input: std::pin::Pin<&items::TextInput>) { let rect: qttypes::QRectF = get_geometry!(pos, items::TextInput, text_input); let color: u32 = text_input.color().as_argb_encoded(); let selection_foreground_color: u32 = text_input.selection_foreground_color().as_argb_encoded(); let selection_background_color: u32 = text_input.selection_background_color().as_argb_encoded(); let string: qttypes::QString = text_input.text().as_str().into(); let font: QFont = get_font(text_input.font_request()); let flags = match text_input.horizontal_alignment() { TextHorizontalAlignment::left => key_generated::Qt_AlignmentFlag_AlignLeft, TextHorizontalAlignment::center => key_generated::Qt_AlignmentFlag_AlignHCenter, TextHorizontalAlignment::right => key_generated::Qt_AlignmentFlag_AlignRight, } | match text_input.vertical_alignment() { TextVerticalAlignment::top => key_generated::Qt_AlignmentFlag_AlignTop, TextVerticalAlignment::center => key_generated::Qt_AlignmentFlag_AlignVCenter, TextVerticalAlignment::bottom => key_generated::Qt_AlignmentFlag_AlignBottom, }; let cursor_position: i32 = text_input.cursor_position(); let anchor_position: i32 = text_input.anchor_position(); let text_cursor_width: f32 = if text_input.cursor_visible() { text_input.text_cursor_width() } else { 0. }; let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [ painter as "QPainter*", rect as "QRectF", color as "QRgb", selection_foreground_color as "QRgb", selection_background_color as "QRgb", string as "QString", flags as "int", font as "QFont", cursor_position as "int", anchor_position as "int", text_cursor_width as "float"] { Q_UNUSED(flags); // FIXME QTextLayout layout(string, font); layout.beginLayout(); layout.createLine(); layout.endLayout(); painter->setPen(QColor{color}); QVector selections; if (anchor_position != cursor_position) { QTextCharFormat fmt; fmt.setBackground(QColor(selection_background_color)); fmt.setForeground(QColor(selection_foreground_color)); selections << QTextLayout::FormatRange{ std::min(anchor_position, cursor_position), std::abs(anchor_position - cursor_position), fmt }; } layout.draw(painter, rect.topLeft(), selections); if (text_cursor_width > 0) { layout.drawCursor(painter, rect.topLeft(), cursor_position, text_cursor_width); } }} } fn draw_path(&mut self, pos: Point, path: Pin<&items::Path>) { let elements = path.elements(); if matches!(elements, PathData::None) { return; } // FIXME: handle width/height //let rect: qttypes::QRectF = get_geometry!(pos, items::Path, path); let pos = qttypes::QPoint { x: (pos.x + path.x()) as _, y: (pos.y + path.y()) as _ }; let fill_color: u32 = path.fill_color().as_argb_encoded(); let stroke_color: u32 = path.stroke_color().as_argb_encoded(); let stroke_width: f32 = path.stroke_width(); let mut painter_path = QPainterPath::default(); for x in elements.iter_fitted(path.width(), path.height()).iter() { impl From for qttypes::QPointF { fn from(p: Point) -> Self { qttypes::QPointF { x: p.x as _, y: p.y as _ } } } match x { lyon_path::Event::Begin { at } => { painter_path.move_to(at.into()); } lyon_path::Event::Line { from: _, to } => { painter_path.line_to(to.into()); } lyon_path::Event::Quadratic { from: _, ctrl, to } => { painter_path.quad_to(ctrl.into(), to.into()); } lyon_path::Event::Cubic { from: _, ctrl1, ctrl2, to } => { painter_path.cubic_to(ctrl1.into(), ctrl2.into(), to.into()); } lyon_path::Event::End { last: _, first: _, close } => { // FIXME: are we supposed to do something with last and first? if close { painter_path.close() } } } } let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [ painter as "QPainter*", pos as "QPoint", mut painter_path as "QPainterPath", fill_color as "QRgb", stroke_color as "QRgb", stroke_width as "float"] { painter->save(); auto cleanup = qScopeGuard([&] { painter->restore(); }); painter->translate(pos); painter->setPen(stroke_width > 0 ? QPen(QColor::fromRgba(stroke_color), stroke_width) : Qt::NoPen); painter->setBrush(QColor::fromRgba(fill_color)); painter->drawPath(painter_path); }} } fn draw_box_shadow(&mut self, pos: Point, box_shadow: Pin<&items::BoxShadow>) { // This could be improved to use a guassian blur. let mut shadow_rect = get_geometry!(pos, items::BoxShadow, box_shadow); shadow_rect.x += box_shadow.offset_x() as f64; shadow_rect.y += box_shadow.offset_y() as f64; self.draw_rectangle_impl( shadow_rect, box_shadow.color(), Color::default(), 0., box_shadow.radius(), ); } fn combine_clip(&mut self, pos: Point, clip: Pin<&items::Clip>) { let clip_rect: qttypes::QRectF = get_geometry!(pos, items::Clip, clip); let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", clip_rect as "QRectF"] { painter->setClipRect(clip_rect, Qt::IntersectClip); }} } fn save_state(&mut self) { self.painter.save_state() } fn restore_state(&mut self) { self.painter.restore_state() } fn scale_factor(&self) -> f32 { return 1.; /* cpp! { unsafe [painter as "QPainter*"] -> f32 as "float" { return painter->paintEngine()->paintDevice()->devicePixelRatioF(); }} */ } fn draw_cached_pixmap( &mut self, _item_cache: &sixtyfps_corelib::item_rendering::CachedRenderingData, pos: Point, update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])), ) { update_fn(&mut |width: u32, height: u32, data: &[u8]| { let pos = qttypes::QPoint { x: pos.x as _, y: pos.y as _ }; let data = data.as_ptr(); let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", pos as "QPoint", width as "int", height as "int", data as "const unsigned char *"] { QImage img(data, width, height, width * 4, QImage::Format_ARGB32_Premultiplied); painter->drawImage(pos, img); }} }) } fn as_any(&mut self) -> &mut dyn std::any::Any { self.painter } } fn load_image_from_resource(resource: Resource) -> Option { let (is_path, data) = match resource { Resource::None => return None, Resource::AbsoluteFilePath(path) => (true, qttypes::QByteArray::from(path.as_str())), Resource::EmbeddedData(data) => (false, qttypes::QByteArray::from(data.as_slice())), Resource::EmbeddedRgbaImage { .. } => todo!(), }; Some(cpp! { unsafe [data as "QByteArray", is_path as "bool"] -> qttypes::QPixmap as "QPixmap" { QPixmap img; is_path ? img.load(QString::fromUtf8(data)) : img.loadFromData(data); return img; }}) } impl QtItemRenderer<'_> { fn draw_image_impl( &mut self, item_cache: &CachedRenderingData, source_property: Pin<&Property>, dest_rect: qttypes::QRectF, source_rect: Option, image_fit: ImageFit, ) { let cached = item_cache.ensure_up_to_date(&mut self.cache.borrow_mut(), || { load_image_from_resource(source_property.get()) .map_or(QtRenderingCacheItem::Invalid, |pixmap| { QtRenderingCacheItem::Pixmap(pixmap) }) }); let pixmap: &qttypes::QPixmap = match &cached { QtRenderingCacheItem::Pixmap(pixmap) => pixmap, _ => return, }; let image_size = pixmap.size(); let mut source_rect = source_rect.unwrap_or_else(|| qttypes::QRectF { x: 0., y: 0., width: image_size.width as _, height: image_size.height as _, }); match image_fit { sixtyfps_corelib::items::ImageFit::fill => (), sixtyfps_corelib::items::ImageFit::contain => { let ratio = qttypes::qreal::max( dest_rect.width / source_rect.width, dest_rect.height / source_rect.height, ); if source_rect.width > dest_rect.width / ratio { source_rect.x += (source_rect.width - dest_rect.width / ratio) / 2.; source_rect.width = dest_rect.width / ratio; } if source_rect.height > dest_rect.height / ratio { source_rect.y += (source_rect.height - dest_rect.height / ratio) / 2.; source_rect.height = dest_rect.height / ratio; } } }; let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", pixmap as "QPixmap*", source_rect as "QRectF", dest_rect as "QRectF"] { painter->drawPixmap(dest_rect, *pixmap, source_rect); }}; } fn draw_rectangle_impl( &mut self, mut rect: qttypes::QRectF, color: Color, border_color: Color, border_width: f32, border_radius: f32, ) { let color: u32 = color.as_argb_encoded(); let border_color: u32 = border_color.as_argb_encoded(); let border_width: f32 = border_width.min((rect.width as f32) / 2.); // adjust the size so that the border is drawn within the geometry rect.x += border_width as f64 / 2.; rect.y += border_width as f64 / 2.; rect.width -= border_width as f64; rect.height -= border_width as f64; let painter: &mut QPainter = &mut *self.painter; cpp! { unsafe [painter as "QPainter*", color as "QRgb", border_color as "QRgb", border_width as "float", border_radius as "float", rect as "QRectF"] { painter->setPen(border_width > 0 ? QPen(QColor::fromRgba(border_color), border_width) : Qt::NoPen); painter->setBrush(QColor::fromRgba(color)); if (border_radius > 0) { painter->drawRoundedRect(rect, border_radius, border_radius); } else { painter->drawRect(rect); } }} } } cpp_class!(unsafe struct QWidgetPtr as "std::unique_ptr"); pub struct QtWindow { widget_ptr: QWidgetPtr, pub(crate) self_weak: once_cell::unsync::OnceCell>, /// Gets dirty when the layout restrictions, or some other property of the windows change meta_property_listener: Pin>, /// Gets dirty if something needs to be painted redraw_listener: Pin>, popup_window: RefCell, ComponentRc)>>, cache: QtRenderingCache, scale_factor: Pin>>, } impl QtWindow { pub fn new() -> Rc { let widget_ptr = cpp! {unsafe [] -> QWidgetPtr as "std::unique_ptr" { ensure_initialized(); return std::make_unique(); }}; let rc = Rc::new(QtWindow { widget_ptr, self_weak: Default::default(), meta_property_listener: Rc::pin(Default::default()), redraw_listener: Rc::pin(Default::default()), popup_window: Default::default(), cache: Default::default(), scale_factor: Box::pin(Property::new(1.)), }); let self_weak = Rc::downgrade(&rc); let widget_ptr = rc.widget_ptr(); let rust_window = Rc::as_ptr(&rc); cpp! {unsafe [widget_ptr as "SixtyFPSWidget*", rust_window as "void*"] { widget_ptr->rust_window = rust_window; }}; ALL_WINDOWS.with(|aw| aw.borrow_mut().push(self_weak)); rc } /// Return the QWidget* fn widget_ptr(&self) -> NonNull<()> { unsafe { std::mem::transmute_copy::>(&self.widget_ptr) } } /// ### Candidate to be moved in corelib as this kind of duplicate GraphicsWindow::draw fn paint_event(&self, painter: &mut QPainter) { sixtyfps_corelib::animations::update_animations(); let component_rc = self.self_weak.get().unwrap().upgrade().unwrap().component(); 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(Default::default()); 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); } }) } let cache = self.cache.clone(); self.redraw_listener.as_ref().evaluate(|| { let mut renderer = QtItemRenderer { painter, cache }; sixtyfps_corelib::item_rendering::render_component_items( &component_rc, &mut renderer, Point::default(), ); }); sixtyfps_corelib::animations::CURRENT_ANIMATION_DRIVER.with(|driver| { if !driver.has_active_animations() { return; } let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*"] { // FIXME: using QTimer -::singleShot is not optimal. We should use Qt animation timer QTimer::singleShot(16, [widget_ptr = QPointer(widget_ptr)] { if (widget_ptr) widget_ptr->update(); }); //return widget_ptr->update(); }} }); } fn resize_event(&self, size: qttypes::QSize) { let component_rc = self.self_weak.get().unwrap().upgrade().unwrap().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::(root_item) { window_item.width.set(size.width as _); window_item.height.set(size.height as _); } } fn mouse_event(&self, what: MouseEventType, pos: qttypes::QPoint) { let pos = Point::new(pos.x as _, pos.y as _); self.self_weak.get().unwrap().upgrade().unwrap().process_mouse_input(pos, what); timer_event(); } fn key_event(&self, key: i32, text: qttypes::QString, modif: u32, released: bool) { sixtyfps_corelib::animations::update_animations(); let text: String = text.into(); let modifiers = sixtyfps_corelib::input::KeyboardModifiers { control: (modif & key_generated::Qt_KeyboardModifier_ControlModifier) != 0, alt: (modif & key_generated::Qt_KeyboardModifier_AltModifier) != 0, shift: (modif & key_generated::Qt_KeyboardModifier_ShiftModifier) != 0, meta: (modif & key_generated::Qt_KeyboardModifier_MetaModifier) != 0, }; let text = qt_key_to_string(key as key_generated::Qt_Key, text); let event = KeyEvent { event_type: if released { KeyEventType::KeyReleased } else { KeyEventType::KeyPressed }, text, modifiers, }; self.self_weak.get().unwrap().upgrade().unwrap().process_key_input(&event); timer_event(); } /// Set the min/max sizes on the QWidget fn apply_geometry_constraint(&self, constraints: sixtyfps_corelib::layout::LayoutInfo) { let widget_ptr = self.widget_ptr(); let min_width: f32 = constraints.min_width.min(constraints.max_width); let min_height: f32 = constraints.min_height.min(constraints.max_height); let mut max_width: f32 = constraints.max_width.max(constraints.min_width); let mut max_height: f32 = constraints.max_height.max(constraints.min_height); cpp! {unsafe [widget_ptr as "QWidget*", min_width as "float", min_height as "float", mut max_width as "float", mut max_height as "float"] { widget_ptr->setMinimumSize(QSize(min_width, min_height)); if (max_width > QWIDGETSIZE_MAX) max_width = QWIDGETSIZE_MAX; if (max_height > QWIDGETSIZE_MAX) max_height = QWIDGETSIZE_MAX; widget_ptr->setMaximumSize(QSize(max_width, max_height)); }}; } /// Apply windows property such as title to the QWidget* fn apply_window_properties(&self, window_item: Pin<&items::Window>) { let widget_ptr = self.widget_ptr(); let title: qttypes::QString = items::Window::FIELD_OFFSETS.title.apply_pin(window_item).get().as_str().into(); cpp! {unsafe [widget_ptr as "QWidget*", title as "QString"] { widget_ptr->setWindowTitle(title); }}; } } #[allow(unused)] impl PlatformWindow for QtWindow { fn show(self: Rc) { let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*"] { widget_ptr->show(); }}; } fn hide(self: Rc) { let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*"] { widget_ptr->hide(); }}; } fn request_redraw(&self) { if self.redraw_listener.is_dirty() { let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*"] { return widget_ptr->update(); }} } } fn scale_factor(&self) -> f32 { self.scale_factor.as_ref().get() /* let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*"] -> f32 as "float" { return widget_ptr->windowHandle()->devicePixelRatio(); }} */ } /// Only used for testing fn set_scale_factor(&self, factor: f32) { self.scale_factor.as_ref().set(factor) } fn get_geometry(&self) -> sixtyfps_corelib::graphics::Rect { // FIXME Default::default() } fn free_graphics_resources<'a>(self: Rc, items: &Slice<'a, Pin>>) { for item in items.iter() { let cached_rendering_data = item.cached_rendering_data_offset(); cached_rendering_data.release(&mut self.cache.borrow_mut()); } } fn show_popup(&self, popup: &sixtyfps_corelib::component::ComponentRc, position: Point) { let popup_window = QtWindow::new(); let window = Rc::new(sixtyfps_corelib::window::Window::new(popup_window.clone())); popup_window.self_weak.set(Rc::downgrade(&window)).ok().unwrap(); window.set_component(popup); let popup_ptr = popup_window.widget_ptr(); let pos = qttypes::QPoint { x: position.x as _, y: position.y as _ }; let widget_ptr = self.widget_ptr(); cpp! {unsafe [widget_ptr as "QWidget*", popup_ptr as "QWidget*", pos as "QPoint"] { popup_ptr->setParent(widget_ptr, Qt::Popup); popup_ptr->move(pos + widget_ptr->pos()); popup_ptr->show(); }}; self.popup_window.replace(Some((window, popup.clone()))); } fn close_popup(&self) { self.popup_window.replace(None); } fn font_metrics( &self, request: FontRequest, ) -> Option> { Some(Box::new(get_font(request))) } fn image_size( &self, _item_graphics_cache: &sixtyfps_corelib::item_rendering::CachedRenderingData, source: Pin<&sixtyfps_corelib::properties::Property>, ) -> sixtyfps_corelib::graphics::Size { load_image_from_resource(source.get()) .map(|img| { let qsize = img.size(); euclid::size2(qsize.width as f32, qsize.height as f32) }) .unwrap_or_default() } } fn get_font(request: FontRequest) -> QFont { let family: qttypes::QString = request.family.as_str().into(); let pixel_size: f32 = request.pixel_size.unwrap_or(0.); let weight: i32 = request.weight.unwrap_or(0); cpp!(unsafe [family as "QString", pixel_size as "float", weight as "int"] -> QFont as "QFont" { QFont f; if (!family.isEmpty()) f.setFamily(family); if (pixel_size > 0) f.setPixelSize(pixel_size); if (weight > 0) f.setWeight(weight); return f; }) } cpp_class! {pub unsafe struct QFont as "QFont"} impl sixtyfps_corelib::graphics::FontMetrics for QFont { fn text_size(&self, text: &str) -> sixtyfps_corelib::graphics::Size { let string = qttypes::QString::from(text); let size = cpp! { unsafe [self as "const QFont*", string as "QString"] -> qttypes::QSizeF as "QSizeF"{ return QFontMetricsF(*self).boundingRect(QRectF(), 0, string).size(); }}; sixtyfps_corelib::graphics::Size::new(size.width as _, size.height as _) } fn text_offset_for_x_position<'a>(&self, text: &'a str, x: f32) -> usize { let string = qttypes::QString::from(text); cpp! { unsafe [self as "const QFont*", string as "QString", x as "float"] -> usize as "long long" { QTextLayout layout(string, *self); layout.beginLayout(); layout.createLine(); layout.endLayout(); if (layout.lineCount() == 0) return 0; auto cur = layout.lineAt(0).xToCursor(x); // convert to an utf8 pos; return QStringView(string).left(cur).toUtf8().size(); }} } fn height(&self) -> f32 { cpp! { unsafe [self as "const QFont*"] -> f32 as "float"{ return QFontMetricsF(*self).height(); }} } } thread_local! { // FIXME: currently the window are never removed static ALL_WINDOWS: RefCell>> = Default::default(); } /// Called by C++'s TimerHandler::timerEvent, or everytime a timer might have been started fn timer_event() { sixtyfps_corelib::animations::update_animations(); sixtyfps_corelib::timers::TimerList::maybe_activate_timers(); ALL_WINDOWS.with(|windows| { for x in windows.borrow().iter() { if let Some(x) = x.upgrade() { x.request_redraw(); } } }); if let Some(instant) = sixtyfps_corelib::timers::TimerList::next_timeout() { let now = std::time::Instant::now(); let timeout = if instant > now { instant.duration_since(now).as_millis() as i32 } else { 0 }; cpp! { unsafe [timeout as "int"] { TimerHandler::instance().timer.start(timeout, &TimerHandler::instance()); }} } } fn qt_key_to_string(key: key_generated::Qt_Key, event_text: String) -> SharedString { // First try to see if we received one of the non-ascii keys that we have // a special representation for. If that fails, try to use the provided // text. If that's empty, then try to see if the provided key has an ascii // representation. The last step is needed because modifiers may result in // the text to be empty otherwise, for example Ctrl+C. if let Some(special_key_code) = match key as key_generated::Qt_Key { key_generated::Qt_Key_Key_Left => Some(InternalKeyCode::Left), key_generated::Qt_Key_Key_Right => Some(InternalKeyCode::Right), key_generated::Qt_Key_Key_Backspace => Some(InternalKeyCode::Back), key_generated::Qt_Key_Key_Delete => Some(InternalKeyCode::Delete), key_generated::Qt_Key_Key_End => Some(InternalKeyCode::End), key_generated::Qt_Key_Key_Home => Some(InternalKeyCode::Home), key_generated::Qt_Key_Key_Return => Some(InternalKeyCode::Return), _ => None, } { return special_key_code.encode_to_string(); }; if !event_text.is_empty() { return event_text.into(); } match key { key_generated::Qt_Key_Key_Space => " ", key_generated::Qt_Key_Key_Exclam => "!", key_generated::Qt_Key_Key_QuoteDbl => "\"", key_generated::Qt_Key_Key_NumberSign => "#", key_generated::Qt_Key_Key_Dollar => "$", key_generated::Qt_Key_Key_Percent => "%", key_generated::Qt_Key_Key_Ampersand => "&", key_generated::Qt_Key_Key_Apostrophe => "'", key_generated::Qt_Key_Key_ParenLeft => "(", key_generated::Qt_Key_Key_ParenRight => ")", key_generated::Qt_Key_Key_Asterisk => "*", key_generated::Qt_Key_Key_Plus => "+", key_generated::Qt_Key_Key_Comma => ",", key_generated::Qt_Key_Key_Minus => "-", key_generated::Qt_Key_Key_Period => ".", key_generated::Qt_Key_Key_Slash => "/", key_generated::Qt_Key_Key_0 => "0", key_generated::Qt_Key_Key_1 => "1", key_generated::Qt_Key_Key_2 => "2", key_generated::Qt_Key_Key_3 => "3", key_generated::Qt_Key_Key_4 => "4", key_generated::Qt_Key_Key_5 => "5", key_generated::Qt_Key_Key_6 => "6", key_generated::Qt_Key_Key_7 => "7", key_generated::Qt_Key_Key_8 => "8", key_generated::Qt_Key_Key_9 => "9", key_generated::Qt_Key_Key_Colon => ":", key_generated::Qt_Key_Key_Semicolon => ";", key_generated::Qt_Key_Key_Less => "<", key_generated::Qt_Key_Key_Equal => "=", key_generated::Qt_Key_Key_Greater => ">", key_generated::Qt_Key_Key_Question => "?", key_generated::Qt_Key_Key_At => "@", key_generated::Qt_Key_Key_A => "a", key_generated::Qt_Key_Key_B => "b", key_generated::Qt_Key_Key_C => "c", key_generated::Qt_Key_Key_D => "d", key_generated::Qt_Key_Key_E => "e", key_generated::Qt_Key_Key_F => "f", key_generated::Qt_Key_Key_G => "g", key_generated::Qt_Key_Key_H => "h", key_generated::Qt_Key_Key_I => "i", key_generated::Qt_Key_Key_J => "j", key_generated::Qt_Key_Key_K => "k", key_generated::Qt_Key_Key_L => "l", key_generated::Qt_Key_Key_M => "m", key_generated::Qt_Key_Key_N => "n", key_generated::Qt_Key_Key_O => "o", key_generated::Qt_Key_Key_P => "p", key_generated::Qt_Key_Key_Q => "q", key_generated::Qt_Key_Key_R => "r", key_generated::Qt_Key_Key_S => "s", key_generated::Qt_Key_Key_T => "t", key_generated::Qt_Key_Key_U => "u", key_generated::Qt_Key_Key_V => "v", key_generated::Qt_Key_Key_W => "w", key_generated::Qt_Key_Key_X => "x", key_generated::Qt_Key_Key_Y => "y", key_generated::Qt_Key_Key_Z => "z", key_generated::Qt_Key_Key_BracketLeft => "[", key_generated::Qt_Key_Key_Backslash => "\\", key_generated::Qt_Key_Key_BracketRight => "]", key_generated::Qt_Key_Key_AsciiCircum => "^", key_generated::Qt_Key_Key_Underscore => "_", key_generated::Qt_Key_Key_QuoteLeft => "`", key_generated::Qt_Key_Key_BraceLeft => "{", key_generated::Qt_Key_Key_Bar => "|", key_generated::Qt_Key_Key_BraceRight => "}", key_generated::Qt_Key_Key_AsciiTilde => "~", _ => "", } .into() }