From 937e1e696cd923be68664e0b7b3e93331c60b6c0 Mon Sep 17 00:00:00 2001 From: Imbris Date: Tue, 26 May 2020 18:26:16 -0400 Subject: [PATCH] Add support for Button widget, new custom widget AspectRatioContainer --- voxygen/src/menu/main/ui.rs | 60 ++++-- voxygen/src/ui/ice/mod.rs | 3 +- .../ui/ice/renderer/aspect_ratio_container.rs | 25 +++ voxygen/src/ui/ice/renderer/button.rs | 165 +++++++++++++++ voxygen/src/ui/ice/renderer/image.rs | 9 +- .../ui/ice/{renderer.rs => renderer/mod.rs} | 29 ++- voxygen/src/ui/ice/renderer/text.rs | 28 +-- voxygen/src/ui/ice/widget.rs | 1 + .../ui/ice/widget/aspect_ratio_container.rs | 189 ++++++++++++++++++ .../src/ui/ice/widget/background_container.rs | 29 ++- voxygen/src/ui/ice/widget/compound_graphic.rs | 4 +- voxygen/src/ui/ice/widget/image.rs | 4 +- 12 files changed, 500 insertions(+), 46 deletions(-) create mode 100644 voxygen/src/ui/ice/renderer/aspect_ratio_container.rs create mode 100644 voxygen/src/ui/ice/renderer/button.rs rename voxygen/src/ui/ice/{renderer.rs => renderer/mod.rs} (97%) create mode 100644 voxygen/src/ui/ice/widget/aspect_ratio_container.rs diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index db29d8e19f..b10e64aad1 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -158,6 +158,7 @@ rotation_image_ids! { } } +#[derive(Clone)] // TODO: why does iced require Clone? pub enum Event { LoginAttempt { username: String, @@ -187,26 +188,59 @@ pub struct PopupData { // No state currently struct IcedState { imgs: IcedImgs, + quit_button: iced::button::State, } pub type Message = Event; impl IcedState { + pub fn new(imgs: IcedImgs) -> Self { + Self { + imgs, + quit_button: iced::button::State::new(), + } + } + pub fn view(&mut self, i18n: &Localization) -> Element { - use iced::{Align, Column, Container, Length, Row, Space, Text}; + // TODO: scale with window size + let button_font_size = 30; + const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0); + const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2); + + use iced::{ + Align, Button, Column, Container, HorizontalAlignment, Length, Row, Space, Text, + VerticalAlignment, + }; use ui::ice::{ compound_graphic::{CompoundGraphic, Graphic}, - BackgroundContainer, Image, Padding, + AspectRatioContainer, BackgroundContainer, ButtonStyle, Image, Padding, }; use vek::*; + let button_style = ButtonStyle::new(self.imgs.button) + .hover_image(self.imgs.button_hover) + .press_image(self.imgs.button_press) + .text_color(TEXT_COLOR) + .disabled_text_color(DISABLED_TEXT_COLOR); + let buttons = Column::with_children(vec![ Image::new(self.imgs.button).fix_aspect_ratio().into(), Image::new(self.imgs.button).fix_aspect_ratio().into(), - /* BackgroundContainer::new( - Image::new(self.imgs.button).fix_aspect_ratio(), - Text::new("Quit"), + AspectRatioContainer::new( + Button::new( + &mut self.quit_button, + Text::new(i18n.get("common.quit")) + .size(button_font_size) + .height(Length::Fill) + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + ) + .height(Length::Fill) + .width(Length::Fill) + .style(button_style) + .on_press(Message::Quit), ) - .into(), */ - Text::new(i18n.get("common.quit")).size(40).into(), + .ratio_of_image(self.imgs.button) + .into(), ]) .width(Length::Fill) .max_width(200) @@ -314,13 +348,7 @@ impl IcedState { .height(Length::Fill) .spacing(10); - BackgroundContainer::new( - Image::new(self.imgs.bg) - .width(Length::Fill) - .height(Length::Fill), - content, - ) - .into() + BackgroundContainer::new(Image::new(self.imgs.bg), content).into() } pub fn update(message: Message) { @@ -413,9 +441,7 @@ impl<'a> MainMenuUi { }; let mut ice_ui = IcedUi::new(window, ice_font).unwrap(); - let ice_state = IcedState { - imgs: IcedImgs::load(&mut ice_ui).expect("Failed to load images"), - }; + let ice_state = IcedState::new(IcedImgs::load(&mut ice_ui).expect("Failed to load images")); Self { ui, diff --git a/voxygen/src/ui/ice/mod.rs b/voxygen/src/ui/ice/mod.rs index 7d24651b5c..a3e8745b98 100644 --- a/voxygen/src/ui/ice/mod.rs +++ b/voxygen/src/ui/ice/mod.rs @@ -8,8 +8,9 @@ mod winit_conversion; pub use cache::Font; pub use graphic::{Id, Rotation}; pub use iced::Event; -pub use renderer::IcedRenderer; +pub use renderer::{ButtonStyle, IcedRenderer}; pub use widget::{ + aspect_ratio_container::AspectRatioContainer, background_container::{BackgroundContainer, Padding}, compound_graphic, image::Image, diff --git a/voxygen/src/ui/ice/renderer/aspect_ratio_container.rs b/voxygen/src/ui/ice/renderer/aspect_ratio_container.rs new file mode 100644 index 0000000000..71c0f13f3b --- /dev/null +++ b/voxygen/src/ui/ice/renderer/aspect_ratio_container.rs @@ -0,0 +1,25 @@ +use super::{ + super::widget::{aspect_ratio_container, image}, + IcedRenderer, +}; +use iced::{Element, Layout, Point, Rectangle}; + +impl aspect_ratio_container::Renderer for IcedRenderer { + //type Style + type ImageHandle = image::Handle; + + fn dimensions(&self, handle: &Self::ImageHandle) -> (u32, u32) { self.image_dims(*handle) } + + fn draw( + &mut self, + defaults: &Self::Defaults, + _bounds: Rectangle, + cursor_position: Point, + //style: &Self::Style, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + // TODO: stlying to add a background image and such + content.draw(self, defaults, content_layout, cursor_position) + } +} diff --git a/voxygen/src/ui/ice/renderer/button.rs b/voxygen/src/ui/ice/renderer/button.rs new file mode 100644 index 0000000000..30754d512f --- /dev/null +++ b/voxygen/src/ui/ice/renderer/button.rs @@ -0,0 +1,165 @@ +use super::{super::Rotation, widget::image, Defaults, IcedRenderer, Primitive}; +use iced::{button, mouse, Color, Element, Layout, Point, Rectangle}; +use vek::Rgba; + +#[derive(Clone, Copy)] +struct Background { + default: image::Handle, + hover: image::Handle, + press: image::Handle, +} + +impl Background { + fn new(image: image::Handle) -> Self { + Self { + default: image, + hover: image, + press: image, + } + } +} +// TODO: consider a different place for this +// Note: for now all buttons have an image background +#[derive(Clone, Copy)] +pub struct Style { + background: Option, + enabled_text: Color, + disabled_text: Color, + /* greying out / changing text color + *disabled: , */ +} + +impl Style { + pub fn new(image: image::Handle) -> Self { + Self { + background: Some(Background::new(image)), + ..Default::default() + } + } + + pub fn hover_image(mut self, image: image::Handle) -> Self { + self.background = Some(match self.background { + Some(mut background) => { + background.hover = image; + background + }, + None => Background::new(image), + }); + self + } + + pub fn press_image(mut self, image: image::Handle) -> Self { + self.background = Some(match self.background { + Some(mut background) => { + background.press = image; + background + }, + None => Background::new(image), + }); + self + } + + pub fn text_color(mut self, color: Color) -> Self { + self.enabled_text = color; + self + } + + pub fn disabled_text_color(mut self, color: Color) -> Self { + self.disabled_text = color; + self + } + + fn disabled(&self) -> (Option, Color) { + ( + self.background.as_ref().map(|b| b.default), + self.disabled_text, + ) + } + + fn pressed(&self) -> (Option, Color) { + (self.background.as_ref().map(|b| b.press), self.enabled_text) + } + + fn hovered(&self) -> (Option, Color) { + (self.background.as_ref().map(|b| b.hover), self.enabled_text) + } + + fn active(&self) -> (Option, Color) { + ( + self.background.as_ref().map(|b| b.default), + self.enabled_text, + ) + } +} + +impl Default for Style { + fn default() -> Self { + Self { + background: None, + enabled_text: Color::WHITE, + disabled_text: Color::from_rgb(0.5, 0.5, 0.5), + } + } +} + +impl button::Renderer for IcedRenderer { + // TODO: what if this gets large enough to not be copied around? + type Style = Style; + + const DEFAULT_PADDING: u16 = 0; + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + cursor_position: Point, + is_disabled: bool, + is_pressed: bool, + style: &Self::Style, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let (maybe_image, text_color) = if is_disabled { + style.disabled() + } else if is_mouse_over { + if is_pressed { + style.pressed() + } else { + style.hovered() + } + } else { + style.active() + }; + + let (content, _) = content.draw( + self, + &Defaults { text_color }, + content_layout, + cursor_position, + ); + + let primitive = if let Some(handle) = maybe_image { + let background = Primitive::Image { + handle: (handle, Rotation::None), + bounds, + color: Rgba::broadcast(255), + }; + + Primitive::Group { + primitives: vec![background, content], + } + } else { + content + }; + + let mouse_interaction = if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }; + + (primitive, mouse_interaction) + } +} diff --git a/voxygen/src/ui/ice/renderer/image.rs b/voxygen/src/ui/ice/renderer/image.rs index 710bf386b7..829bef748b 100644 --- a/voxygen/src/ui/ice/renderer/image.rs +++ b/voxygen/src/ui/ice/renderer/image.rs @@ -6,14 +6,7 @@ use iced::mouse; use vek::Rgba; impl image::Renderer for IcedRenderer { - fn dimensions(&self, handle: image::Handle) -> (u32, u32) { - self - .cache - .graphic_cache() - .get_graphic_dims((handle, Rotation::None)) - // TODO: don't unwrap - .unwrap() - } + fn dimensions(&self, handle: image::Handle) -> (u32, u32) { self.image_dims(handle) } fn draw( &mut self, diff --git a/voxygen/src/ui/ice/renderer.rs b/voxygen/src/ui/ice/renderer/mod.rs similarity index 97% rename from voxygen/src/ui/ice/renderer.rs rename to voxygen/src/ui/ice/renderer/mod.rs index 04d7f01830..3ed49040c3 100644 --- a/voxygen/src/ui/ice/renderer.rs +++ b/voxygen/src/ui/ice/renderer/mod.rs @@ -1,4 +1,7 @@ +// TODO: reorganize modules (e.g. put all these in a widget submodule) +mod aspect_ratio_container; mod background_container; +mod button; mod column; mod compound_graphic; mod container; @@ -7,6 +10,8 @@ mod row; mod space; mod text; +pub use button::Style as ButtonStyle; + use super::{ super::graphic::{self, Graphic, TexId}, cache::Cache, @@ -157,6 +162,15 @@ impl IcedRenderer { self.cache.add_graphic(graphic) } + fn image_dims(&self, handle: super::widget::image::Handle) -> (u32, u32) { + self + .cache + .graphic_cache() + .get_graphic_dims((handle, Rotation::None)) + // TODO: don't unwrap + .unwrap() + } + pub fn resize(&mut self, scaled_dims: Vec2, renderer: &mut Renderer) { self.win_dims = scaled_dims; @@ -582,9 +596,22 @@ fn default_scissor(renderer: &Renderer) -> Aabr { } } +// TODO: expose to user +pub struct Defaults { + pub text_color: iced::Color, +} + +impl Default for Defaults { + fn default() -> Self { + Self { + text_color: iced::Color::WHITE, + } + } +} + impl iced::Renderer for IcedRenderer { // Default styling - type Defaults = (); + type Defaults = Defaults; // TODO: use graph of primitives to enable diffing??? type Output = (Primitive, iced::mouse::Interaction); diff --git a/voxygen/src/ui/ice/renderer/text.rs b/voxygen/src/ui/ice/renderer/text.rs index a8067c860c..5ffb25d44d 100644 --- a/voxygen/src/ui/ice/renderer/text.rs +++ b/voxygen/src/ui/ice/renderer/text.rs @@ -28,7 +28,7 @@ impl text::Renderer for IcedRenderer { fn draw( &mut self, - _defaults: &Self::Defaults, + defaults: &Self::Defaults, bounds: Rectangle, content: &str, size: u16, @@ -38,25 +38,27 @@ impl text::Renderer for IcedRenderer { vertical_alignment: VerticalAlignment, ) -> Self::Output { use glyph_brush::{HorizontalAlign, VerticalAlign}; - let h_align = match horizontal_alignment { - HorizontalAlignment::Left => HorizontalAlign::Left, - HorizontalAlignment::Center => HorizontalAlign::Center, - HorizontalAlignment::Right => HorizontalAlign::Right, + // glyph_brush thought it would be a great idea to change what the bounds and + // position mean based on the alignment + // TODO: add option to align based on the geometry of the rendered glyphs + // instead of all possible glyphs + let (x, h_align) = match horizontal_alignment { + HorizontalAlignment::Left => (bounds.x, HorizontalAlign::Left), + HorizontalAlignment::Center => (bounds.center_x(), HorizontalAlign::Center), + HorizontalAlignment::Right => (bounds.x + bounds.width, HorizontalAlign::Right), }; - let v_align = match vertical_alignment { - VerticalAlignment::Top => VerticalAlign::Top, - VerticalAlignment::Center => VerticalAlign::Center, - VerticalAlignment::Bottom => VerticalAlign::Bottom, + let (y, v_align) = match vertical_alignment { + VerticalAlignment::Top => (bounds.y, VerticalAlign::Top), + VerticalAlignment::Center => (bounds.center_y(), VerticalAlign::Center), + VerticalAlignment::Bottom => (bounds.y + bounds.height, VerticalAlign::Bottom), }; let p_scale = self.p_scale; let section = glyph_brush::Section { text: content, - // TODO: do snap to pixel thing here IF it is being done down the line - //screen_position: (bounds.x * p_scale, (self.win_dims.y - bounds.y) * p_scale), - screen_position: (bounds.x * p_scale, bounds.y * p_scale), + screen_position: (x * p_scale, y * p_scale), bounds: (bounds.width * p_scale, bounds.height * p_scale), scale: glyph_brush::rusttype::Scale::uniform(size as f32 * p_scale), layout: glyph_brush::Layout::Wrap { @@ -86,7 +88,7 @@ impl text::Renderer for IcedRenderer { glyphs, //size: size as f32, bounds, - linear_color: color.unwrap_or(Color::BLACK).into_linear().into(), + linear_color: color.unwrap_or(defaults.text_color).into_linear().into(), /*font, *horizontal_alignment, *vertical_alignment, */ diff --git a/voxygen/src/ui/ice/widget.rs b/voxygen/src/ui/ice/widget.rs index ebf8ee659b..85b4968588 100644 --- a/voxygen/src/ui/ice/widget.rs +++ b/voxygen/src/ui/ice/widget.rs @@ -1,3 +1,4 @@ +pub mod aspect_ratio_container; pub mod background_container; pub mod compound_graphic; pub mod image; diff --git a/voxygen/src/ui/ice/widget/aspect_ratio_container.rs b/voxygen/src/ui/ice/widget/aspect_ratio_container.rs new file mode 100644 index 0000000000..f92832f987 --- /dev/null +++ b/voxygen/src/ui/ice/widget/aspect_ratio_container.rs @@ -0,0 +1,189 @@ +use iced::{layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Widget}; +use std::{hash::Hash, u32}; + +// Note: it might be more efficient to make this generic over the content type? + +enum AspectRatio { + /// Image Id + Image(I), + /// width / height + Ratio(f32), +} + +impl Hash for AspectRatio { + fn hash(&self, state: &mut H) { + match self { + Self::Image(i) => i.hash(state), + Self::Ratio(r) => r.to_bits().hash(state), + } + } +} + +/// Provides a container that takes on a fixed aspect ratio +/// Thus, can be used to fix the aspect ratio of content if it is set to +/// Length::Fill The aspect ratio may be based on that of an image, in which +/// case the ratio is obtained from the renderer +pub struct AspectRatioContainer<'a, M, R: self::Renderer> { + max_width: u32, + max_height: u32, + aspect_ratio: AspectRatio, + content: Element<'a, M, R>, +} + +impl<'a, M, R> AspectRatioContainer<'a, M, R> +where + R: self::Renderer, +{ + pub fn new(content: impl Into>) -> Self { + Self { + max_width: u32::MAX, + max_height: u32::MAX, + aspect_ratio: AspectRatio::Ratio(1.0), + content: content.into(), + } + } + + /// Set the ratio (width/height) + pub fn ratio(mut self, ratio: f32) -> Self { + self.aspect_ratio = AspectRatio::Ratio(ratio); + self + } + + /// Use the ratio of the provided image + pub fn ratio_of_image(mut self, handle: R::ImageHandle) -> Self { + self.aspect_ratio = AspectRatio::Image(handle); + self + } + + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } +} + +impl<'a, M, R> Widget for AspectRatioContainer<'a, M, R> +where + R: self::Renderer, +{ + fn width(&self) -> Length { Length::Fill } + + fn height(&self) -> Length { Length::Fill } + + fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node { + let limits = limits + .loose() + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width()) + .height(self.height()); + + let aspect_ratio = match &self.aspect_ratio { + AspectRatio::Image(handle) => { + let (pixel_w, pixel_h) = renderer.dimensions(handle); + + // Just in case + // could convert to gracefully handling + debug_assert!(pixel_w != 0); + debug_assert!(pixel_h != 0); + + pixel_w as f32 / pixel_h as f32 + }, + AspectRatio::Ratio(ratio) => *ratio, + }; + + // We need to figure out the max width/height of the limits + // and then adjust one down to meet the aspect ratio + let max_size = limits.max(); + let (max_width, max_height) = (max_size.width as f32, max_size.height as f32); + let max_aspect_ratio = max_width / max_height; + let limits = if max_aspect_ratio > aspect_ratio { + limits.max_width((max_height * aspect_ratio) as u32) + } else { + limits.max_height((max_width / aspect_ratio) as u32) + }; + + let content = self.content.layout(renderer, &limits.loose()); + + layout::Node::with_children(limits.max(), vec![content]) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &R, + clipboard: Option<&dyn Clipboard>, + ) { + self.content.on_event( + event, + layout.children().next().unwrap(), + cursor_position, + messages, + renderer, + clipboard, + ); + } + + fn draw( + &self, + renderer: &mut R, + defaults: &R::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> R::Output { + renderer.draw( + defaults, + layout.bounds(), + cursor_position, + &self.content, + layout.children().next().unwrap(), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.max_width.hash(state); + self.max_height.hash(state); + self.aspect_ratio.hash(state); + // TODO: add pixel dims (need renderer) + + self.content.hash_layout(state); + } +} + +pub trait Renderer: iced::Renderer { + /// The style supported by this renderer. + //type Style: Default; + /// The handle used by this renderer for images. + type ImageHandle: Hash; + + fn dimensions(&self, handle: &Self::ImageHandle) -> (u32, u32); + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + cursor_position: Point, + //style: &Self::Style, + content: &Element<'_, M, Self>, + content_layout: Layout<'_>, + ) -> Self::Output; +} + +// They got to live ¯\_(ツ)_/¯ +impl<'a, M, R> From> for Element<'a, M, R> +where + R: 'a + self::Renderer, + M: 'a, +{ + fn from(widget: AspectRatioContainer<'a, M, R>) -> Element<'a, M, R> { Element::new(widget) } +} diff --git a/voxygen/src/ui/ice/widget/background_container.rs b/voxygen/src/ui/ice/widget/background_container.rs index a4fa05f7a6..af94fbdedb 100644 --- a/voxygen/src/ui/ice/widget/background_container.rs +++ b/voxygen/src/ui/ice/widget/background_container.rs @@ -1,4 +1,4 @@ -use iced::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; +use iced::{layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, Widget}; use std::{hash::Hash, u32}; // Note: it might be more efficient to make this generic over the content type @@ -63,7 +63,7 @@ pub trait Background: Sized { fn width(&self) -> Length; fn height(&self) -> Length; fn aspect_ratio_fixed(&self) -> bool; - fn pixel_dims(&self, renderer: &R) -> [u16; 2]; + fn pixel_dims(&self, renderer: &R) -> (u16, u16); fn draw( &self, renderer: &mut R, @@ -158,7 +158,7 @@ where .width(self.width()) .height(self.height()); - let [pixel_w, pixel_h] = self.background.pixel_dims(renderer); + let (pixel_w, pixel_h) = self.background.pixel_dims(renderer); let (horizontal_pad_frac, vertical_pad_frac, top_pad_frac, left_pad_frac) = { let Padding { top, @@ -208,6 +208,10 @@ where // again, why is loose() used here? let mut content = self.content.layout(renderer, &limits.loose()); + // TODO: handle cases where self and/or children are not Length::Fill + // If fill use max_size + //if match self.width(), self.height() + // This time we need to adjust up to meet the aspect ratio // so that the container is larger than the contents let mut content_size = content.size(); @@ -261,6 +265,25 @@ where layout::Node::with_children(size, vec![content]) } + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &R, + clipboard: Option<&dyn Clipboard>, + ) { + self.content.on_event( + event, + layout.children().next().unwrap(), + cursor_position, + messages, + renderer, + clipboard, + ); + } + fn draw( &self, renderer: &mut R, diff --git a/voxygen/src/ui/ice/widget/compound_graphic.rs b/voxygen/src/ui/ice/widget/compound_graphic.rs index bae79a6bef..7382edabde 100644 --- a/voxygen/src/ui/ice/widget/compound_graphic.rs +++ b/voxygen/src/ui/ice/widget/compound_graphic.rs @@ -230,7 +230,9 @@ where fn aspect_ratio_fixed(&self) -> bool { self.fix_aspect_ratio } - fn pixel_dims(&self, _renderer: &R) -> [u16; 2] { self.graphics_size } + fn pixel_dims(&self, _renderer: &R) -> (u16, u16) { + (self.graphics_size[0], self.graphics_size[1]) + } fn draw( &self, diff --git a/voxygen/src/ui/ice/widget/image.rs b/voxygen/src/ui/ice/widget/image.rs index 22eb73e15d..4a74bf0c81 100644 --- a/voxygen/src/ui/ice/widget/image.rs +++ b/voxygen/src/ui/ice/widget/image.rs @@ -123,9 +123,9 @@ where fn aspect_ratio_fixed(&self) -> bool { self.fix_aspect_ratio } - fn pixel_dims(&self, renderer: &R) -> [u16; 2] { + fn pixel_dims(&self, renderer: &R) -> (u16, u16) { let (w, h) = renderer.dimensions(self.handle); - [w as u16, h as u16] + (w as u16, h as u16) } fn draw(