From 2991f61b929386405847632dce5696f5662ff4d0 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 16 Apr 2020 00:47:29 -0700 Subject: [PATCH 01/33] Got it to commit for ios runtiem --- Cargo.toml | 6 +- ios/.gitignore | 1 + ios/Cargo.toml | 22 +++++++ ios/src/lib.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++ src/application.rs | 39 ++++++++++- src/element.rs | 6 +- src/lib.rs | 19 ++++-- src/settings.rs | 2 +- src/widget.rs | 5 +- 9 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 ios/.gitignore create mode 100644 ios/Cargo.toml create mode 100644 ios/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 4dd7d1e87a..d02a077801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ members = [ "native", "style", "web", + "ios", "wgpu", "winit", "examples/bezier_tool", @@ -74,7 +75,7 @@ members = [ iced_core = { version = "0.2", path = "core" } iced_futures = { version = "0.1", path = "futures" } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))'.dependencies] iced_winit = { version = "0.1", path = "winit" } iced_glutin = { version = "0.1", path = "glutin", optional = true } iced_wgpu = { version = "0.2", path = "wgpu", optional = true } @@ -83,6 +84,9 @@ iced_glow = { version = "0.1", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.2", path = "web" } +[target.'cfg(target_os = "ios")'.dependencies] +iced_ios = { version = "0.1", path = "ios" } + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas"] diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000000..03314f77b5 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/ios/Cargo.toml b/ios/Cargo.toml new file mode 100644 index 0000000000..2e6ddc4871 --- /dev/null +++ b/ios/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "iced_ios" +version = "0.1.0" +authors = ["Sebastian Imlay "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +twox-hash = "1.5" +raw-window-handle = "0.3" +unicode-segmentation = "1.6" +winit = "0.22" + +[dependencies.iced_core] +version = "0.2" +path = "../core" + +[dependencies.iced_futures] +version = "0.1" +path = "../futures" +features = ["thread-pool"] diff --git a/ios/src/lib.rs b/ios/src/lib.rs new file mode 100644 index 0000000000..dafcba71b2 --- /dev/null +++ b/ios/src/lib.rs @@ -0,0 +1,159 @@ +pub use iced_futures::{executor, futures, Command}; + +#[doc(no_inline)] +pub use executor::Executor; + +pub type Align = (); +pub type Background = (); +pub type Color = (); +pub type Font = (); +pub type HorizontalAlignment = (); +pub type Length = (); +pub type Point = (); +pub type Size = (); + +//pub type Subscription = iced_futures::Subscription; +pub type Subscription = iced_futures::Subscription; +pub type Vector = (); +pub type VerticalAlignment = (); + +#[allow(missing_debug_implementations)] +pub struct Element<'a, Message> { + pub(crate) widget: Box + 'a>, +} + +pub trait Widget { +} + +pub trait Application: Sized { + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Flags; + + /// Initializes the [`Application`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Application`]: trait.Application.html + fn new(flags: Self::Flags) -> (Self, Command) + where + Self: Sized; + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Runs the [`Application`]. + /// + /// [`Application`]: trait.Application.html + fn run(flags: Self::Flags) + where + Self: 'static + Sized, + { + use winit::{ + event::{self, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + //let event_loop = EventLoop::with_user_event(); + let event_loop = EventLoop::new(); + /* + let mut runtime = { + let executor = Self::Executor::new().expect("Create executor"); + + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + }; + let (mut application, init_command) = + runtime.enter(|| Self::new(flags)); + runtime.spawn(init_command); + let mut title = application.title(); + let mut mode = application.mode(); + */ + + let window = { + let mut window_builder = WindowBuilder::new(); + + //let (width, height) = settings.window.size; + + window_builder = window_builder + .with_title("foobar"); + /* + .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_resizable(settings.window.resizable) + .with_decorations(settings.window.decorations); + */ + window_builder.build(&event_loop).expect("Open window") + }; + + window.request_redraw(); + + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + window.request_redraw(); + } + event::Event::UserEvent(message) => { + //external_messages.push(message); + } + event::Event::RedrawRequested(_) => { + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} diff --git a/src/application.rs b/src/application.rs index 19cab7da3d..5e691fa778 100644 --- a/src/application.rs +++ b/src/application.rs @@ -186,7 +186,7 @@ pub trait Application: Sized { where Self: 'static, { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, @@ -207,12 +207,15 @@ pub trait Application: Sized { #[cfg(target_arch = "wasm32")] as iced_web::Application>::run(settings.flags); + + #[cfg(target_os = "ios")] + as iced_ios::Application>::run(settings.flags); } } struct Instance(A); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl iced_winit::Program for Instance where A: Application, @@ -289,3 +292,35 @@ where self.0.view() } } + +#[cfg(target_os = "ios")] +impl iced_ios::Application for Instance +where + A: Application, +{ + type Executor = A::Executor; + type Message = A::Message; + type Flags = A::Flags; + + fn new(flags: Self::Flags) -> (Self, Command) { + let (app, command) = A::new(flags); + + (Instance(app), command) + } + + fn title(&self) -> String { + self.0.title() + } + + fn update(&mut self, message: Self::Message) -> Command { + self.0.update(message) + } + + fn subscription(&self) -> Subscription { + self.0.subscription() + } + + fn view(&mut self) -> Element<'_, Self::Message> { + self.0.view() + } +} diff --git a/src/element.rs b/src/element.rs index 6f47c70126..a0a9c510dd 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,9 +1,13 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] pub type Element<'a, Message> = crate::runtime::Element<'a, Message, crate::renderer::Renderer>; +#[cfg(target_os = "ios")] +pub use iced_ios::Element; + #[cfg(target_arch = "wasm32")] pub use iced_web::Element; + diff --git a/src/lib.rs b/src/lib.rs index d08b39cfb5..07122fb1b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,10 +173,10 @@ //! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ //! [`Application`]: trait.Application.html //! [`Sandbox`]: trait.Sandbox.html -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![deny(unused_results)] -#![forbid(unsafe_code)] +//#![deny(missing_docs)] +//#![deny(missing_debug_implementations)] +//#![deny(unused_results)] +//#![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod application; @@ -199,16 +199,22 @@ pub mod time; #[cfg(all( not(target_arch = "wasm32"), + not(target_arch = "ios"), not(feature = "glow"), feature = "wgpu" ))] use iced_winit as runtime; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(all( + not(target_arch = "wasm32"), + not(target_arch = "ios"), + feature = "glow") + )] use iced_glutin as runtime; #[cfg(all( not(target_arch = "wasm32"), + not(target_arch = "ios"), not(feature = "glow"), feature = "wgpu" ))] @@ -220,6 +226,9 @@ use iced_glow as renderer; #[cfg(target_arch = "wasm32")] use iced_web as runtime; +#[cfg(target_os = "ios")] +use iced_ios as runtime; + #[doc(no_inline)] pub use widget::*; diff --git a/src/settings.rs b/src/settings.rs index 01ad0ee044..bd4e9fa0cd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -50,7 +50,7 @@ impl Settings { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { diff --git a/src/widget.rs b/src/widget.rs index 3e4d478861..1f06611054 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -16,7 +16,7 @@ //! //! [`TextInput`]: text_input/struct.TextInput.html //! [`text_input::State`]: text_input/struct.State.html -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, progress_bar, radio, @@ -59,5 +59,8 @@ mod platform { mod platform { pub use iced_web::widget::*; } +#[cfg(target_os = "ios")] +mod platform { +} pub use platform::*; From 7384becb2457095cef1ea5c896c37513a9fa864e Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 01:06:33 -0700 Subject: [PATCH 02/33] Added ios example and ios things --- examples/ios-example/Cargo.toml | 24 ++++ examples/ios-example/src/main.rs | 54 +++++++++ ios/Cargo.toml | 1 + ios/src/application.rs | 163 +++++++++++++++++++++++++ ios/src/bus.rs | 56 +++++++++ ios/src/layout.rs | 151 ++++++++++++++++++++++++ ios/src/lib.rs | 196 +++++++------------------------ ios/src/proxy.rs | 58 +++++++++ ios/src/widget.rs | 32 +++++ ios/src/widget/checkbox.rs | 39 ++++++ ios/src/widget/container.rs | 52 ++++++++ 11 files changed, 673 insertions(+), 153 deletions(-) create mode 100644 examples/ios-example/Cargo.toml create mode 100644 examples/ios-example/src/main.rs create mode 100644 ios/src/application.rs create mode 100644 ios/src/bus.rs create mode 100644 ios/src/layout.rs create mode 100644 ios/src/proxy.rs create mode 100644 ios/src/widget.rs create mode 100644 ios/src/widget/checkbox.rs create mode 100644 ios/src/widget/container.rs diff --git a/examples/ios-example/Cargo.toml b/examples/ios-example/Cargo.toml new file mode 100644 index 0000000000..1e3d9a0ba2 --- /dev/null +++ b/examples/ios-example/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ios-example" +version = "0.1.0" +authors = ["Sebastian Imlay "] +edition = "2018" +description = "" + +[package.metadata.bundle] +name = "ios-example" +identifier = "com.github.iced.simple" +category = "Utility" +short_description = "An example of a bundled application" +long_description = """ +A trivial application that just displays a blank window with +a title bar. It serves as an example of an application that +can be bundled with cargo-bundle, as well as a test-case for +cargo-bundle's support for bundling crate examples. +""" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { path = "../..", features = ["image", "debug"] } +env_logger = "0.7" diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs new file mode 100644 index 0000000000..4bad7f0f0f --- /dev/null +++ b/examples/ios-example/src/main.rs @@ -0,0 +1,54 @@ +use iced::{ + //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, + //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, + //Sandbox, //Scrollable, Settings, Slider, Space, Text, TextInput, + Application, + Settings, + Command, + executor, + Element, + Container, + Checkbox, +}; +pub fn main() { + env_logger::init(); + + Simple::run(Settings::default()) +} + +#[derive(Debug, Default)] +pub struct Simple { + enabled: bool, +} +#[derive(Debug, Clone)] +pub enum Message { + //EventOccurred(iced_native::Event), + Toggled(bool), +} + +impl Application for Simple { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(flags: Self::Flags) -> (Self, Command) { + (Self::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Events - Iced") + } + + fn update(&mut self, message: Message) -> Command { + Command::none() + } + + fn view(&mut self) -> Element { + let toggle = Checkbox::new( + self.enabled, + "Listen to runtime events", + Message::Toggled, + ); + toggle.into() + } +} diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 2e6ddc4871..290217989e 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -11,6 +11,7 @@ twox-hash = "1.5" raw-window-handle = "0.3" unicode-segmentation = "1.6" winit = "0.22" +uikit-sys = { path = "../../../rust-ios/uikit-sys/" } [dependencies.iced_core] version = "0.2" diff --git a/ios/src/application.rs b/ios/src/application.rs new file mode 100644 index 0000000000..a03116f833 --- /dev/null +++ b/ios/src/application.rs @@ -0,0 +1,163 @@ +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription}; +use winit::{ + event::{self, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopProxy}, + platform::ios::{EventLoopExtIOS, WindowBuilderExtIOS, WindowExtIOS}, + window::WindowBuilder, +}; +use uikit_sys::{ + self, + //CGRect, + //CGPoint, + //CGSize, + id, + IUIColor, + //IUISwitch, + //IUIView, + UIColor, + //UIView_UIViewGeometry, + //UISwitch, + UIView, + //UIView_UIViewHierarchy, + //UIView, + //UIViewController, + UIView_UIViewRendering, +}; + +pub trait Application: Sized { + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Flags; + + /// Initializes the [`Application`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Application`]: trait.Application.html + fn new(flags: Self::Flags) -> (Self, Command) + where + Self: Sized; + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Runs the [`Application`]. + /// + /// [`Application`]: trait.Application.html + fn run(flags: Self::Flags) + where + Self: 'static + Sized, + { + + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = Self::Executor::new().expect("Create executor"); + + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + }; + + let (app, command) = runtime.enter(|| Self::new(flags)); + + let title = app.title(); + + let window = { + let mut window_builder = WindowBuilder::new(); + + //let (width, height) = settings.window.size; + + window_builder = window_builder + .with_title(title) + .with_maximized(true) + .with_fullscreen(None) + //.with_inner_size(winit::dpi::LogicalSize { width: 100, height: 100}) + ; + /* + .with_resizable(settings.window.resizable) + .with_decorations(settings.window.decorations); + */ + window_builder.build(&event_loop).expect("Open window") + }; + + window.request_redraw(); + + let root_view: UIView = UIView(window.ui_view() as id); + unsafe { + let color = UIColor::alloc(); + let background = UIColor( + color.initWithRed_green_blue_alpha_(0.1, 1.0, 2.0, 2.0), + ); + root_view.setBackgroundColor_(background.0); + } + //let root_view: UIView = UIView(window.ui_view() as id); + //add_views(root_view); + + event_loop.run(move |event: winit::event::Event<()>, _, control_flow| match event { + event::Event::MainEventsCleared => { + window.request_redraw(); + } + event::Event::UserEvent(_message) => { + //external_messages.push(message); + } + event::Event::RedrawRequested(_) => {} + event::Event::WindowEvent { + event: _window_event, + .. + } => { + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} diff --git a/ios/src/bus.rs b/ios/src/bus.rs new file mode 100644 index 0000000000..351b159d4b --- /dev/null +++ b/ios/src/bus.rs @@ -0,0 +1,56 @@ + +use iced_futures::futures::channel::mpsc; +use std::rc::Rc; + +/// A publisher of messages. +/// +/// It can be used to route messages back to the [`Application`]. +/// +/// [`Application`]: trait.Application.html +#[allow(missing_debug_implementations)] +pub struct Bus { + publish: Rc ()>>, +} + +impl Clone for Bus { + fn clone(&self) -> Self { + Bus { + publish: self.publish.clone(), + } + } +} + +impl Bus +where + Message: 'static, +{ + pub(crate) fn new(publish: mpsc::UnboundedSender) -> Self { + Self { + publish: Rc::new(Box::new(move |message| { + publish.unbounded_send(message).expect("Send message"); + })), + } + } + + /// Publishes a new message for the [`Application`]. + /// + /// [`Application`]: trait.Application.html + pub fn publish(&self, message: Message) { + (self.publish)(message) + } + + /// Creates a new [`Bus`] that applies the given function to the messages + /// before publishing. + /// + /// [`Bus`]: struct.Bus.html + pub fn map(&self, mapper: Rc Message>>) -> Bus + where + B: 'static, + { + let publish = self.publish.clone(); + + Bus { + publish: Rc::new(Box::new(move |message| publish(mapper(message)))), + } + } +} diff --git a/ios/src/layout.rs b/ios/src/layout.rs new file mode 100644 index 0000000000..3dc5a9072f --- /dev/null +++ b/ios/src/layout.rs @@ -0,0 +1,151 @@ + +use crate::{Align, Point, Rectangle, Size, Vector}; + +/// The bounds of an element and its children. +#[derive(Debug, Clone, Default)] +pub struct Node { + bounds: Rectangle, + children: Vec, +} + +impl Node { + /// Creates a new [`Node`] with the given [`Size`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn new(size: Size) -> Self { + Self::with_children(size, Vec::new()) + } + + /// Creates a new [`Node`] with the given [`Size`] and children. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn with_children(size: Size, children: Vec) -> Self { + Node { + bounds: Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + children, + } + } + + /// Returns the [`Size`] of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub fn size(&self) -> Size { + Size::new(self.bounds.width, self.bounds.height) + } + + /// Returns the bounds of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + self.bounds + } + + /// Returns the children of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn children(&self) -> &[Node] { + &self.children + } + + /// Aligns the [`Node`] in the given space. + /// + /// [`Node`]: struct.Node.html + pub fn align( + &mut self, + horizontal_alignment: Align, + vertical_alignment: Align, + space: Size, + ) { + match horizontal_alignment { + Align::Start => {} + Align::Center => { + self.bounds.x += (space.width - self.bounds.width) / 2.0; + } + Align::End => { + self.bounds.x += space.width - self.bounds.width; + } + } + + match vertical_alignment { + Align::Start => {} + Align::Center => { + self.bounds.y += (space.height - self.bounds.height) / 2.0; + } + Align::End => { + self.bounds.y += space.height - self.bounds.height; + } + } + } + + /// Moves the [`Node`] to the given position. + /// + /// [`Node`]: struct.Node.html + pub fn move_to(&mut self, position: Point) { + self.bounds.x = position.x; + self.bounds.y = position.y; + } +} + +/// The bounds of a [`Node`] and its children, using absolute coordinates. +/// +/// [`Node`]: struct.Node.html +#[derive(Debug, Clone, Copy)] +pub struct Layout<'a> { + position: Point, + node: &'a Node, +} + +impl<'a> Layout<'a> { + pub(crate) fn new(node: &'a Node) -> Self { + Self::with_offset(Vector::new(0.0, 0.0), node) + } + + pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { + let bounds = node.bounds(); + + Self { + position: Point::new(bounds.x, bounds.y) + offset, + node, + } + } + + /// Gets the bounds of the [`Layout`]. + /// + /// The returned [`Rectangle`] describes the position and size of a + /// [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Rectangle`]: struct.Rectangle.html + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + let bounds = self.node.bounds(); + + Rectangle { + x: self.position.x, + y: self.position.y, + width: bounds.width, + height: bounds.height, + } + } + + /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Node`]: struct.Node.html + pub fn children(&'a self) -> impl Iterator> { + self.node.children().iter().map(move |node| { + Layout::with_offset( + Vector::new(self.position.x, self.position.y), + node, + ) + }) + } +} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index dafcba71b2..09d826c453 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -2,158 +2,48 @@ pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] pub use executor::Executor; - -pub type Align = (); -pub type Background = (); -pub type Color = (); -pub type Font = (); -pub type HorizontalAlignment = (); -pub type Length = (); -pub type Point = (); -pub type Size = (); - -//pub type Subscription = iced_futures::Subscription; -pub type Subscription = iced_futures::Subscription; -pub type Vector = (); -pub type VerticalAlignment = (); - -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message> { - pub(crate) widget: Box + 'a>, -} - -pub trait Widget { +mod application; +pub mod widget; +pub use widget::{ + Element, Widget, +}; +mod proxy; +mod bus; +use proxy::Proxy; +mod layout; +pub use layout::Layout; +pub use application::Application; +/* +//! Run commands and subscriptions. +use crate::{Event, Hasher}; + +/// A native runtime with a generic executor and receiver of results. +/// +/// It can be used by shells to easily spawn a [`Command`] or track a +/// [`Subscription`]. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: ../struct.Subscription.html +pub type Runtime = + iced_futures::Runtime; +*/ + +#[derive(PartialEq, Clone, Debug)] +pub enum Event { } -pub trait Application: Sized { - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; - - /// The data needed to initialize your [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Flags; - - /// Initializes the [`Application`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`](struct.Command.html) if you - /// need to perform some async action in the background on startup. This is - /// useful if you want to load state from a file, perform an initial HTTP - /// request, etc. - /// - /// [`Application`]: trait.Application.html - fn new(flags: Self::Flags) -> (Self, Command) - where - Self: Sized; - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - /// - /// [`Application`]: trait.Application.html - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - /// - /// [`Application`]: trait.Application.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command; - - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Application`]: trait.Application.html - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - /// - /// [`Subscription`]: struct.Subscription.html - fn subscription(&self) -> Subscription { - Subscription::none() - } - - /// Runs the [`Application`]. - /// - /// [`Application`]: trait.Application.html - fn run(flags: Self::Flags) - where - Self: 'static + Sized, - { - use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - //let event_loop = EventLoop::with_user_event(); - let event_loop = EventLoop::new(); - /* - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); - - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); - let mut title = application.title(); - let mut mode = application.mode(); - */ - - let window = { - let mut window_builder = WindowBuilder::new(); - - //let (width, height) = settings.window.size; - - window_builder = window_builder - .with_title("foobar"); - /* - .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_resizable(settings.window.resizable) - .with_decorations(settings.window.decorations); - */ - window_builder.build(&event_loop).expect("Open window") - }; - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - window.request_redraw(); - } - event::Event::UserEvent(message) => { - //external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - } - _ => { - *control_flow = ControlFlow::Wait; - } - }) - } -} +pub type Runtime = + iced_futures::Runtime< + std::collections::hash_map::DefaultHasher, + Event, Executor, Receiver, Message>; + +pub type Subscription = iced_futures::Subscription< + std::collections::hash_map::DefaultHasher, + Event, + T, +>; + +pub use iced_core::{ + Align, Background, Color, Font, HorizontalAlignment, Length, Point, + Rectangle, Size, Vector, VerticalAlignment, +}; diff --git a/ios/src/proxy.rs b/ios/src/proxy.rs new file mode 100644 index 0000000000..2683688936 --- /dev/null +++ b/ios/src/proxy.rs @@ -0,0 +1,58 @@ +use iced_futures::futures::{ + channel::mpsc, + task::{Context, Poll}, + Sink, +}; +use std::pin::Pin; + +pub struct Proxy { + raw: winit::event_loop::EventLoopProxy, +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + } + } +} + +impl Proxy { + pub fn new(raw: winit::event_loop::EventLoopProxy) -> Self { + Self { raw } + } +} + +impl Sink for Proxy { + type Error = mpsc::SendError; + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send( + self: Pin<&mut Self>, + message: Message, + ) -> Result<(), Self::Error> { + let _ = self.raw.send_event(message); + + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/ios/src/widget.rs b/ios/src/widget.rs new file mode 100644 index 0000000000..1ad0eef87f --- /dev/null +++ b/ios/src/widget.rs @@ -0,0 +1,32 @@ +use crate::{ + Layout, +}; +pub mod container; +pub mod checkbox; + +pub use container::Container; +pub use checkbox::Checkbox; +pub trait Widget { + fn draw( + &self, + _layout: Layout<'_>, + ) { + } +} + +#[allow(missing_debug_implementations)] +pub struct Element<'a, Message> { + pub(crate) widget: Box + 'a>, +} +impl<'a, Message> Element<'a, Message> { + /// Create a new [`Element`] containing the given [`Widget`]. + /// + /// [`Element`]: struct.Element.html + /// [`Widget`]: widget/trait.Widget.html + pub fn new(widget: impl Widget + 'a) -> Self { + Self { + widget: Box::new(widget), + } + } +} + diff --git a/ios/src/widget/checkbox.rs b/ios/src/widget/checkbox.rs new file mode 100644 index 0000000000..ccc098a674 --- /dev/null +++ b/ios/src/widget/checkbox.rs @@ -0,0 +1,39 @@ +use crate::{Element, Widget, Length, Align}; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Checkbox { + is_checked: bool, + on_toggle: Rc Message>, + label: String, + width: Length, + //style: Box, +} + +impl Checkbox { + pub fn new(is_checked: bool, label: impl Into, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Checkbox { + is_checked, + on_toggle: Rc::new(f), + label: label.into(), + width: Length::Shrink, + //style: Default::default(), + } + } +} +impl Widget for Checkbox +where + Message: 'static, +{ +} +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(checkbox: Checkbox) -> Element<'a, Message> { + Element::new(checkbox) + } +} diff --git a/ios/src/widget/container.rs b/ios/src/widget/container.rs new file mode 100644 index 0000000000..aa4bf48f21 --- /dev/null +++ b/ios/src/widget/container.rs @@ -0,0 +1,52 @@ +use crate::{Element, Widget, Length, Align}; + +#[allow(missing_debug_implementations)] +pub struct Container<'a, Message> { + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: Align, + vertical_alignment: Align, + //style_sheet: Box, + content: Element<'a, Message>, +} + +impl<'a, Message> Container<'a, Message> { + /// Creates an empty [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn new(content: T) -> Self + where + T: Into>, + { + use std::u32; + + Container { + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + horizontal_alignment: Align::Start, + vertical_alignment: Align::Start, + //style_sheet: Default::default(), + content: content.into(), + } + } +} +impl<'a, Message> Widget for Container<'a, Message> +where + Message: 'static, +{ +} +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(container: Container<'a, Message>) -> Element<'a, Message> { + Element::new(container) + } +} + From 88e0ffe50476ee2a311c98baeb8034640b037dc2 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 13:10:39 -0700 Subject: [PATCH 03/33] Moar updates --- Cargo.toml | 1 + src/widget.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d02a077801..594814108d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ members = [ "style", "web", "ios", + "examples/ios-example", "wgpu", "winit", "examples/bezier_tool", diff --git a/src/widget.rs b/src/widget.rs index 1f06611054..4c08dacc61 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -61,6 +61,7 @@ mod platform { } #[cfg(target_os = "ios")] mod platform { + pub use iced_ios::widget::*; } pub use platform::*; From 98fd67c155f294c7547a9438e832d63eb01519a0 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 22:31:50 -0700 Subject: [PATCH 04/33] Updates from rebase --- ios/src/application.rs | 6 +++--- ios/src/keyboard.rs | 4 ++++ ios/src/lib.rs | 2 ++ ios/src/mouse.rs | 4 ++++ src/application.rs | 2 +- src/lib.rs | 17 ++++------------- 6 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 ios/src/keyboard.rs create mode 100644 ios/src/mouse.rs diff --git a/ios/src/application.rs b/ios/src/application.rs index a03116f833..f1a93c27c1 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,4 +1,4 @@ -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription}; +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, Event as IphoneEvent}; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopProxy}, @@ -100,7 +100,7 @@ pub trait Application: Sized { Self: 'static + Sized, { - let event_loop = EventLoop::with_user_event(); + let event_loop : EventLoop = EventLoop::with_user_event(); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -142,7 +142,7 @@ pub trait Application: Sized { //let root_view: UIView = UIView(window.ui_view() as id); //add_views(root_view); - event_loop.run(move |event: winit::event::Event<()>, _, control_flow| match event { + event_loop.run(move |event: winit::event::Event, _, control_flow| match event { event::Event::MainEventsCleared => { window.request_redraw(); } diff --git a/ios/src/keyboard.rs b/ios/src/keyboard.rs new file mode 100644 index 0000000000..26137171c9 --- /dev/null +++ b/ios/src/keyboard.rs @@ -0,0 +1,4 @@ + +pub type Event = (); +pub type KeyCode = (); +pub type ModifiersState = (); diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 09d826c453..129efb52e8 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -4,6 +4,8 @@ pub use iced_futures::{executor, futures, Command}; pub use executor::Executor; mod application; pub mod widget; +pub mod keyboard; +pub mod mouse; pub use widget::{ Element, Widget, }; diff --git a/ios/src/mouse.rs b/ios/src/mouse.rs new file mode 100644 index 0000000000..b295d019ab --- /dev/null +++ b/ios/src/mouse.rs @@ -0,0 +1,4 @@ +pub type Button = (); +pub type Event = (); +pub type Interaction = (); +pub type ScrollDelta = (); diff --git a/src/application.rs b/src/application.rs index 5e691fa778..9be93c88b2 100644 --- a/src/application.rs +++ b/src/application.rs @@ -232,7 +232,7 @@ where } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl crate::runtime::Application for Instance where A: Application, diff --git a/src/lib.rs b/src/lib.rs index 07122fb1b8..c8185c49fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,28 +199,19 @@ pub mod time; #[cfg(all( not(target_arch = "wasm32"), - not(target_arch = "ios"), + not(target_os = "ios"), not(feature = "glow"), feature = "wgpu" ))] use iced_winit as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(target_arch = "ios"), - feature = "glow") - )] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "glow"))] use iced_glutin as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(target_arch = "ios"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), not(feature = "glow"), feature = "wgpu"))] use iced_wgpu as renderer; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "glow"))] use iced_glow as renderer; #[cfg(target_arch = "wasm32")] From 1d5decb2fdc691d8fc07158d59152ca286a76f8b Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 16 Apr 2020 00:47:29 -0700 Subject: [PATCH 05/33] Got it to commit for ios runtiem --- Cargo.toml | 6 +- ios/.gitignore | 1 + ios/Cargo.toml | 22 +++++++ ios/src/lib.rs | 159 +++++++++++++++++++++++++++++++++++++++++++++ src/application.rs | 39 ++++++++++- src/element.rs | 6 +- src/lib.rs | 19 ++++-- src/settings.rs | 2 +- src/widget.rs | 5 +- 9 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 ios/.gitignore create mode 100644 ios/Cargo.toml create mode 100644 ios/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9ab57bc818..06c10e49f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "native", "style", "web", + "ios", "wgpu", "winit", "examples/bezier_tool", @@ -78,7 +79,7 @@ members = [ iced_core = { version = "0.2", path = "core" } iced_futures = { version = "0.1", path = "futures" } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))'.dependencies] iced_winit = { version = "0.1", path = "winit" } iced_glutin = { version = "0.1", path = "glutin", optional = true } iced_wgpu = { version = "0.2", path = "wgpu", optional = true } @@ -87,6 +88,9 @@ iced_glow = { version = "0.1", path = "glow", optional = true} [target.'cfg(target_arch = "wasm32")'.dependencies] iced_web = { version = "0.2", path = "web" } +[target.'cfg(target_os = "ios")'.dependencies] +iced_ios = { version = "0.1", path = "ios" } + [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = ["image", "svg", "canvas"] diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000000..03314f77b5 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1 @@ +Cargo.lock diff --git a/ios/Cargo.toml b/ios/Cargo.toml new file mode 100644 index 0000000000..2e6ddc4871 --- /dev/null +++ b/ios/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "iced_ios" +version = "0.1.0" +authors = ["Sebastian Imlay "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +twox-hash = "1.5" +raw-window-handle = "0.3" +unicode-segmentation = "1.6" +winit = "0.22" + +[dependencies.iced_core] +version = "0.2" +path = "../core" + +[dependencies.iced_futures] +version = "0.1" +path = "../futures" +features = ["thread-pool"] diff --git a/ios/src/lib.rs b/ios/src/lib.rs new file mode 100644 index 0000000000..dafcba71b2 --- /dev/null +++ b/ios/src/lib.rs @@ -0,0 +1,159 @@ +pub use iced_futures::{executor, futures, Command}; + +#[doc(no_inline)] +pub use executor::Executor; + +pub type Align = (); +pub type Background = (); +pub type Color = (); +pub type Font = (); +pub type HorizontalAlignment = (); +pub type Length = (); +pub type Point = (); +pub type Size = (); + +//pub type Subscription = iced_futures::Subscription; +pub type Subscription = iced_futures::Subscription; +pub type Vector = (); +pub type VerticalAlignment = (); + +#[allow(missing_debug_implementations)] +pub struct Element<'a, Message> { + pub(crate) widget: Box + 'a>, +} + +pub trait Widget { +} + +pub trait Application: Sized { + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Flags; + + /// Initializes the [`Application`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Application`]: trait.Application.html + fn new(flags: Self::Flags) -> (Self, Command) + where + Self: Sized; + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Runs the [`Application`]. + /// + /// [`Application`]: trait.Application.html + fn run(flags: Self::Flags) + where + Self: 'static + Sized, + { + use winit::{ + event::{self, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + //let event_loop = EventLoop::with_user_event(); + let event_loop = EventLoop::new(); + /* + let mut runtime = { + let executor = Self::Executor::new().expect("Create executor"); + + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + }; + let (mut application, init_command) = + runtime.enter(|| Self::new(flags)); + runtime.spawn(init_command); + let mut title = application.title(); + let mut mode = application.mode(); + */ + + let window = { + let mut window_builder = WindowBuilder::new(); + + //let (width, height) = settings.window.size; + + window_builder = window_builder + .with_title("foobar"); + /* + .with_inner_size(winit::dpi::LogicalSize { width, height }) + .with_resizable(settings.window.resizable) + .with_decorations(settings.window.decorations); + */ + window_builder.build(&event_loop).expect("Open window") + }; + + window.request_redraw(); + + event_loop.run(move |event, _, control_flow| match event { + event::Event::MainEventsCleared => { + window.request_redraw(); + } + event::Event::UserEvent(message) => { + //external_messages.push(message); + } + event::Event::RedrawRequested(_) => { + } + event::Event::WindowEvent { + event: window_event, + .. + } => { + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} diff --git a/src/application.rs b/src/application.rs index 19cab7da3d..5e691fa778 100644 --- a/src/application.rs +++ b/src/application.rs @@ -186,7 +186,7 @@ pub trait Application: Sized { where Self: 'static, { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] { let renderer_settings = crate::renderer::Settings { default_font: settings.default_font, @@ -207,12 +207,15 @@ pub trait Application: Sized { #[cfg(target_arch = "wasm32")] as iced_web::Application>::run(settings.flags); + + #[cfg(target_os = "ios")] + as iced_ios::Application>::run(settings.flags); } } struct Instance(A); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl iced_winit::Program for Instance where A: Application, @@ -289,3 +292,35 @@ where self.0.view() } } + +#[cfg(target_os = "ios")] +impl iced_ios::Application for Instance +where + A: Application, +{ + type Executor = A::Executor; + type Message = A::Message; + type Flags = A::Flags; + + fn new(flags: Self::Flags) -> (Self, Command) { + let (app, command) = A::new(flags); + + (Instance(app), command) + } + + fn title(&self) -> String { + self.0.title() + } + + fn update(&mut self, message: Self::Message) -> Command { + self.0.update(message) + } + + fn subscription(&self) -> Subscription { + self.0.subscription() + } + + fn view(&mut self) -> Element<'_, Self::Message> { + self.0.view() + } +} diff --git a/src/element.rs b/src/element.rs index 6f47c70126..a0a9c510dd 100644 --- a/src/element.rs +++ b/src/element.rs @@ -1,9 +1,13 @@ /// A generic widget. /// /// This is an alias of an `iced_native` element with a default `Renderer`. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] pub type Element<'a, Message> = crate::runtime::Element<'a, Message, crate::renderer::Renderer>; +#[cfg(target_os = "ios")] +pub use iced_ios::Element; + #[cfg(target_arch = "wasm32")] pub use iced_web::Element; + diff --git a/src/lib.rs b/src/lib.rs index d08b39cfb5..07122fb1b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,10 +173,10 @@ //! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ //! [`Application`]: trait.Application.html //! [`Sandbox`]: trait.Sandbox.html -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![deny(unused_results)] -#![forbid(unsafe_code)] +//#![deny(missing_docs)] +//#![deny(missing_debug_implementations)] +//#![deny(unused_results)] +//#![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod application; @@ -199,16 +199,22 @@ pub mod time; #[cfg(all( not(target_arch = "wasm32"), + not(target_arch = "ios"), not(feature = "glow"), feature = "wgpu" ))] use iced_winit as runtime; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(all( + not(target_arch = "wasm32"), + not(target_arch = "ios"), + feature = "glow") + )] use iced_glutin as runtime; #[cfg(all( not(target_arch = "wasm32"), + not(target_arch = "ios"), not(feature = "glow"), feature = "wgpu" ))] @@ -220,6 +226,9 @@ use iced_glow as renderer; #[cfg(target_arch = "wasm32")] use iced_web as runtime; +#[cfg(target_os = "ios")] +use iced_ios as runtime; + #[doc(no_inline)] pub use widget::*; diff --git a/src/settings.rs b/src/settings.rs index 01ad0ee044..bd4e9fa0cd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -50,7 +50,7 @@ impl Settings { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From> for iced_winit::Settings { fn from(settings: Settings) -> iced_winit::Settings { iced_winit::Settings { diff --git a/src/widget.rs b/src/widget.rs index 3e4d478861..1f06611054 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -16,7 +16,7 @@ //! //! [`TextInput`]: text_input/struct.TextInput.html //! [`text_input::State`]: text_input/struct.State.html -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, progress_bar, radio, @@ -59,5 +59,8 @@ mod platform { mod platform { pub use iced_web::widget::*; } +#[cfg(target_os = "ios")] +mod platform { +} pub use platform::*; From 4a8748f4e0c4c5a82f6fbdc1d0e909592308a398 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 01:06:33 -0700 Subject: [PATCH 06/33] Added ios example and ios things --- examples/ios-example/Cargo.toml | 24 ++++ examples/ios-example/src/main.rs | 54 +++++++++ ios/Cargo.toml | 1 + ios/src/application.rs | 163 +++++++++++++++++++++++++ ios/src/bus.rs | 56 +++++++++ ios/src/layout.rs | 151 ++++++++++++++++++++++++ ios/src/lib.rs | 196 +++++++------------------------ ios/src/proxy.rs | 58 +++++++++ ios/src/widget.rs | 32 +++++ ios/src/widget/checkbox.rs | 39 ++++++ ios/src/widget/container.rs | 52 ++++++++ 11 files changed, 673 insertions(+), 153 deletions(-) create mode 100644 examples/ios-example/Cargo.toml create mode 100644 examples/ios-example/src/main.rs create mode 100644 ios/src/application.rs create mode 100644 ios/src/bus.rs create mode 100644 ios/src/layout.rs create mode 100644 ios/src/proxy.rs create mode 100644 ios/src/widget.rs create mode 100644 ios/src/widget/checkbox.rs create mode 100644 ios/src/widget/container.rs diff --git a/examples/ios-example/Cargo.toml b/examples/ios-example/Cargo.toml new file mode 100644 index 0000000000..1e3d9a0ba2 --- /dev/null +++ b/examples/ios-example/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ios-example" +version = "0.1.0" +authors = ["Sebastian Imlay "] +edition = "2018" +description = "" + +[package.metadata.bundle] +name = "ios-example" +identifier = "com.github.iced.simple" +category = "Utility" +short_description = "An example of a bundled application" +long_description = """ +A trivial application that just displays a blank window with +a title bar. It serves as an example of an application that +can be bundled with cargo-bundle, as well as a test-case for +cargo-bundle's support for bundling crate examples. +""" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced = { path = "../..", features = ["image", "debug"] } +env_logger = "0.7" diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs new file mode 100644 index 0000000000..4bad7f0f0f --- /dev/null +++ b/examples/ios-example/src/main.rs @@ -0,0 +1,54 @@ +use iced::{ + //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, + //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, + //Sandbox, //Scrollable, Settings, Slider, Space, Text, TextInput, + Application, + Settings, + Command, + executor, + Element, + Container, + Checkbox, +}; +pub fn main() { + env_logger::init(); + + Simple::run(Settings::default()) +} + +#[derive(Debug, Default)] +pub struct Simple { + enabled: bool, +} +#[derive(Debug, Clone)] +pub enum Message { + //EventOccurred(iced_native::Event), + Toggled(bool), +} + +impl Application for Simple { + type Executor = executor::Default; + type Message = Message; + type Flags = (); + + fn new(flags: Self::Flags) -> (Self, Command) { + (Self::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Events - Iced") + } + + fn update(&mut self, message: Message) -> Command { + Command::none() + } + + fn view(&mut self) -> Element { + let toggle = Checkbox::new( + self.enabled, + "Listen to runtime events", + Message::Toggled, + ); + toggle.into() + } +} diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 2e6ddc4871..290217989e 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -11,6 +11,7 @@ twox-hash = "1.5" raw-window-handle = "0.3" unicode-segmentation = "1.6" winit = "0.22" +uikit-sys = { path = "../../../rust-ios/uikit-sys/" } [dependencies.iced_core] version = "0.2" diff --git a/ios/src/application.rs b/ios/src/application.rs new file mode 100644 index 0000000000..a03116f833 --- /dev/null +++ b/ios/src/application.rs @@ -0,0 +1,163 @@ +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription}; +use winit::{ + event::{self, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopProxy}, + platform::ios::{EventLoopExtIOS, WindowBuilderExtIOS, WindowExtIOS}, + window::WindowBuilder, +}; +use uikit_sys::{ + self, + //CGRect, + //CGPoint, + //CGSize, + id, + IUIColor, + //IUISwitch, + //IUIView, + UIColor, + //UIView_UIViewGeometry, + //UISwitch, + UIView, + //UIView_UIViewHierarchy, + //UIView, + //UIViewController, + UIView_UIViewRendering, +}; + +pub trait Application: Sized { + type Executor: Executor; + + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// The data needed to initialize your [`Application`]. + /// + /// [`Application`]: trait.Application.html + type Flags; + + /// Initializes the [`Application`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Application`]: trait.Application.html + fn new(flags: Self::Flags) -> (Self, Command) + where + Self: Sized; + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription { + Subscription::none() + } + + /// Runs the [`Application`]. + /// + /// [`Application`]: trait.Application.html + fn run(flags: Self::Flags) + where + Self: 'static + Sized, + { + + let event_loop = EventLoop::with_user_event(); + let mut runtime = { + let executor = Self::Executor::new().expect("Create executor"); + + Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + }; + + let (app, command) = runtime.enter(|| Self::new(flags)); + + let title = app.title(); + + let window = { + let mut window_builder = WindowBuilder::new(); + + //let (width, height) = settings.window.size; + + window_builder = window_builder + .with_title(title) + .with_maximized(true) + .with_fullscreen(None) + //.with_inner_size(winit::dpi::LogicalSize { width: 100, height: 100}) + ; + /* + .with_resizable(settings.window.resizable) + .with_decorations(settings.window.decorations); + */ + window_builder.build(&event_loop).expect("Open window") + }; + + window.request_redraw(); + + let root_view: UIView = UIView(window.ui_view() as id); + unsafe { + let color = UIColor::alloc(); + let background = UIColor( + color.initWithRed_green_blue_alpha_(0.1, 1.0, 2.0, 2.0), + ); + root_view.setBackgroundColor_(background.0); + } + //let root_view: UIView = UIView(window.ui_view() as id); + //add_views(root_view); + + event_loop.run(move |event: winit::event::Event<()>, _, control_flow| match event { + event::Event::MainEventsCleared => { + window.request_redraw(); + } + event::Event::UserEvent(_message) => { + //external_messages.push(message); + } + event::Event::RedrawRequested(_) => {} + event::Event::WindowEvent { + event: _window_event, + .. + } => { + } + _ => { + *control_flow = ControlFlow::Wait; + } + }) + } +} diff --git a/ios/src/bus.rs b/ios/src/bus.rs new file mode 100644 index 0000000000..351b159d4b --- /dev/null +++ b/ios/src/bus.rs @@ -0,0 +1,56 @@ + +use iced_futures::futures::channel::mpsc; +use std::rc::Rc; + +/// A publisher of messages. +/// +/// It can be used to route messages back to the [`Application`]. +/// +/// [`Application`]: trait.Application.html +#[allow(missing_debug_implementations)] +pub struct Bus { + publish: Rc ()>>, +} + +impl Clone for Bus { + fn clone(&self) -> Self { + Bus { + publish: self.publish.clone(), + } + } +} + +impl Bus +where + Message: 'static, +{ + pub(crate) fn new(publish: mpsc::UnboundedSender) -> Self { + Self { + publish: Rc::new(Box::new(move |message| { + publish.unbounded_send(message).expect("Send message"); + })), + } + } + + /// Publishes a new message for the [`Application`]. + /// + /// [`Application`]: trait.Application.html + pub fn publish(&self, message: Message) { + (self.publish)(message) + } + + /// Creates a new [`Bus`] that applies the given function to the messages + /// before publishing. + /// + /// [`Bus`]: struct.Bus.html + pub fn map(&self, mapper: Rc Message>>) -> Bus + where + B: 'static, + { + let publish = self.publish.clone(); + + Bus { + publish: Rc::new(Box::new(move |message| publish(mapper(message)))), + } + } +} diff --git a/ios/src/layout.rs b/ios/src/layout.rs new file mode 100644 index 0000000000..3dc5a9072f --- /dev/null +++ b/ios/src/layout.rs @@ -0,0 +1,151 @@ + +use crate::{Align, Point, Rectangle, Size, Vector}; + +/// The bounds of an element and its children. +#[derive(Debug, Clone, Default)] +pub struct Node { + bounds: Rectangle, + children: Vec, +} + +impl Node { + /// Creates a new [`Node`] with the given [`Size`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn new(size: Size) -> Self { + Self::with_children(size, Vec::new()) + } + + /// Creates a new [`Node`] with the given [`Size`] and children. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn with_children(size: Size, children: Vec) -> Self { + Node { + bounds: Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + children, + } + } + + /// Returns the [`Size`] of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub fn size(&self) -> Size { + Size::new(self.bounds.width, self.bounds.height) + } + + /// Returns the bounds of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + self.bounds + } + + /// Returns the children of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn children(&self) -> &[Node] { + &self.children + } + + /// Aligns the [`Node`] in the given space. + /// + /// [`Node`]: struct.Node.html + pub fn align( + &mut self, + horizontal_alignment: Align, + vertical_alignment: Align, + space: Size, + ) { + match horizontal_alignment { + Align::Start => {} + Align::Center => { + self.bounds.x += (space.width - self.bounds.width) / 2.0; + } + Align::End => { + self.bounds.x += space.width - self.bounds.width; + } + } + + match vertical_alignment { + Align::Start => {} + Align::Center => { + self.bounds.y += (space.height - self.bounds.height) / 2.0; + } + Align::End => { + self.bounds.y += space.height - self.bounds.height; + } + } + } + + /// Moves the [`Node`] to the given position. + /// + /// [`Node`]: struct.Node.html + pub fn move_to(&mut self, position: Point) { + self.bounds.x = position.x; + self.bounds.y = position.y; + } +} + +/// The bounds of a [`Node`] and its children, using absolute coordinates. +/// +/// [`Node`]: struct.Node.html +#[derive(Debug, Clone, Copy)] +pub struct Layout<'a> { + position: Point, + node: &'a Node, +} + +impl<'a> Layout<'a> { + pub(crate) fn new(node: &'a Node) -> Self { + Self::with_offset(Vector::new(0.0, 0.0), node) + } + + pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { + let bounds = node.bounds(); + + Self { + position: Point::new(bounds.x, bounds.y) + offset, + node, + } + } + + /// Gets the bounds of the [`Layout`]. + /// + /// The returned [`Rectangle`] describes the position and size of a + /// [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Rectangle`]: struct.Rectangle.html + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + let bounds = self.node.bounds(); + + Rectangle { + x: self.position.x, + y: self.position.y, + width: bounds.width, + height: bounds.height, + } + } + + /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Node`]: struct.Node.html + pub fn children(&'a self) -> impl Iterator> { + self.node.children().iter().map(move |node| { + Layout::with_offset( + Vector::new(self.position.x, self.position.y), + node, + ) + }) + } +} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index dafcba71b2..09d826c453 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -2,158 +2,48 @@ pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] pub use executor::Executor; - -pub type Align = (); -pub type Background = (); -pub type Color = (); -pub type Font = (); -pub type HorizontalAlignment = (); -pub type Length = (); -pub type Point = (); -pub type Size = (); - -//pub type Subscription = iced_futures::Subscription; -pub type Subscription = iced_futures::Subscription; -pub type Vector = (); -pub type VerticalAlignment = (); - -#[allow(missing_debug_implementations)] -pub struct Element<'a, Message> { - pub(crate) widget: Box + 'a>, -} - -pub trait Widget { +mod application; +pub mod widget; +pub use widget::{ + Element, Widget, +}; +mod proxy; +mod bus; +use proxy::Proxy; +mod layout; +pub use layout::Layout; +pub use application::Application; +/* +//! Run commands and subscriptions. +use crate::{Event, Hasher}; + +/// A native runtime with a generic executor and receiver of results. +/// +/// It can be used by shells to easily spawn a [`Command`] or track a +/// [`Subscription`]. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: ../struct.Subscription.html +pub type Runtime = + iced_futures::Runtime; +*/ + +#[derive(PartialEq, Clone, Debug)] +pub enum Event { } -pub trait Application: Sized { - type Executor: Executor; - - /// The type of __messages__ your [`Application`] will produce. - /// - /// [`Application`]: trait.Application.html - type Message: std::fmt::Debug + Send; - - /// The data needed to initialize your [`Application`]. - /// - /// [`Application`]: trait.Application.html - type Flags; - - /// Initializes the [`Application`]. - /// - /// Here is where you should return the initial state of your app. - /// - /// Additionally, you can return a [`Command`](struct.Command.html) if you - /// need to perform some async action in the background on startup. This is - /// useful if you want to load state from a file, perform an initial HTTP - /// request, etc. - /// - /// [`Application`]: trait.Application.html - fn new(flags: Self::Flags) -> (Self, Command) - where - Self: Sized; - - /// Returns the current title of the [`Application`]. - /// - /// This title can be dynamic! The runtime will automatically update the - /// title of your application when necessary. - /// - /// [`Application`]: trait.Application.html - fn title(&self) -> String; - - /// Handles a __message__ and updates the state of the [`Application`]. - /// - /// This is where you define your __update logic__. All the __messages__, - /// produced by either user interactions or commands, will be handled by - /// this method. - /// - /// Any [`Command`] returned will be executed immediately in the background. - /// - /// [`Application`]: trait.Application.html - /// [`Command`]: struct.Command.html - fn update(&mut self, message: Self::Message) -> Command; - - /// Returns the widgets to display in the [`Application`]. - /// - /// These widgets can produce __messages__ based on user interaction. - /// - /// [`Application`]: trait.Application.html - fn view(&mut self) -> Element<'_, Self::Message>; - - /// Returns the event [`Subscription`] for the current state of the - /// application. - /// - /// A [`Subscription`] will be kept alive as long as you keep returning it, - /// and the __messages__ produced will be handled by - /// [`update`](#tymethod.update). - /// - /// By default, this method returns an empty [`Subscription`]. - /// - /// [`Subscription`]: struct.Subscription.html - fn subscription(&self) -> Subscription { - Subscription::none() - } - - /// Runs the [`Application`]. - /// - /// [`Application`]: trait.Application.html - fn run(flags: Self::Flags) - where - Self: 'static + Sized, - { - use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, - }; - //let event_loop = EventLoop::with_user_event(); - let event_loop = EventLoop::new(); - /* - let mut runtime = { - let executor = Self::Executor::new().expect("Create executor"); - - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) - }; - let (mut application, init_command) = - runtime.enter(|| Self::new(flags)); - runtime.spawn(init_command); - let mut title = application.title(); - let mut mode = application.mode(); - */ - - let window = { - let mut window_builder = WindowBuilder::new(); - - //let (width, height) = settings.window.size; - - window_builder = window_builder - .with_title("foobar"); - /* - .with_inner_size(winit::dpi::LogicalSize { width, height }) - .with_resizable(settings.window.resizable) - .with_decorations(settings.window.decorations); - */ - window_builder.build(&event_loop).expect("Open window") - }; - - window.request_redraw(); - - event_loop.run(move |event, _, control_flow| match event { - event::Event::MainEventsCleared => { - window.request_redraw(); - } - event::Event::UserEvent(message) => { - //external_messages.push(message); - } - event::Event::RedrawRequested(_) => { - } - event::Event::WindowEvent { - event: window_event, - .. - } => { - } - _ => { - *control_flow = ControlFlow::Wait; - } - }) - } -} +pub type Runtime = + iced_futures::Runtime< + std::collections::hash_map::DefaultHasher, + Event, Executor, Receiver, Message>; + +pub type Subscription = iced_futures::Subscription< + std::collections::hash_map::DefaultHasher, + Event, + T, +>; + +pub use iced_core::{ + Align, Background, Color, Font, HorizontalAlignment, Length, Point, + Rectangle, Size, Vector, VerticalAlignment, +}; diff --git a/ios/src/proxy.rs b/ios/src/proxy.rs new file mode 100644 index 0000000000..2683688936 --- /dev/null +++ b/ios/src/proxy.rs @@ -0,0 +1,58 @@ +use iced_futures::futures::{ + channel::mpsc, + task::{Context, Poll}, + Sink, +}; +use std::pin::Pin; + +pub struct Proxy { + raw: winit::event_loop::EventLoopProxy, +} + +impl Clone for Proxy { + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + } + } +} + +impl Proxy { + pub fn new(raw: winit::event_loop::EventLoopProxy) -> Self { + Self { raw } + } +} + +impl Sink for Proxy { + type Error = mpsc::SendError; + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send( + self: Pin<&mut Self>, + message: Message, + ) -> Result<(), Self::Error> { + let _ = self.raw.send_event(message); + + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/ios/src/widget.rs b/ios/src/widget.rs new file mode 100644 index 0000000000..1ad0eef87f --- /dev/null +++ b/ios/src/widget.rs @@ -0,0 +1,32 @@ +use crate::{ + Layout, +}; +pub mod container; +pub mod checkbox; + +pub use container::Container; +pub use checkbox::Checkbox; +pub trait Widget { + fn draw( + &self, + _layout: Layout<'_>, + ) { + } +} + +#[allow(missing_debug_implementations)] +pub struct Element<'a, Message> { + pub(crate) widget: Box + 'a>, +} +impl<'a, Message> Element<'a, Message> { + /// Create a new [`Element`] containing the given [`Widget`]. + /// + /// [`Element`]: struct.Element.html + /// [`Widget`]: widget/trait.Widget.html + pub fn new(widget: impl Widget + 'a) -> Self { + Self { + widget: Box::new(widget), + } + } +} + diff --git a/ios/src/widget/checkbox.rs b/ios/src/widget/checkbox.rs new file mode 100644 index 0000000000..ccc098a674 --- /dev/null +++ b/ios/src/widget/checkbox.rs @@ -0,0 +1,39 @@ +use crate::{Element, Widget, Length, Align}; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Checkbox { + is_checked: bool, + on_toggle: Rc Message>, + label: String, + width: Length, + //style: Box, +} + +impl Checkbox { + pub fn new(is_checked: bool, label: impl Into, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Checkbox { + is_checked, + on_toggle: Rc::new(f), + label: label.into(), + width: Length::Shrink, + //style: Default::default(), + } + } +} +impl Widget for Checkbox +where + Message: 'static, +{ +} +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(checkbox: Checkbox) -> Element<'a, Message> { + Element::new(checkbox) + } +} diff --git a/ios/src/widget/container.rs b/ios/src/widget/container.rs new file mode 100644 index 0000000000..aa4bf48f21 --- /dev/null +++ b/ios/src/widget/container.rs @@ -0,0 +1,52 @@ +use crate::{Element, Widget, Length, Align}; + +#[allow(missing_debug_implementations)] +pub struct Container<'a, Message> { + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: Align, + vertical_alignment: Align, + //style_sheet: Box, + content: Element<'a, Message>, +} + +impl<'a, Message> Container<'a, Message> { + /// Creates an empty [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn new(content: T) -> Self + where + T: Into>, + { + use std::u32; + + Container { + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + horizontal_alignment: Align::Start, + vertical_alignment: Align::Start, + //style_sheet: Default::default(), + content: content.into(), + } + } +} +impl<'a, Message> Widget for Container<'a, Message> +where + Message: 'static, +{ +} +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(container: Container<'a, Message>) -> Element<'a, Message> { + Element::new(container) + } +} + From 7e6d7e209cc53a894bbd5792edf4e78af084834d Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 13:10:39 -0700 Subject: [PATCH 07/33] Moar updates --- Cargo.toml | 1 + src/widget.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 06c10e49f0..31cfd850ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "style", "web", "ios", + "examples/ios-example", "wgpu", "winit", "examples/bezier_tool", diff --git a/src/widget.rs b/src/widget.rs index 1f06611054..4c08dacc61 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -61,6 +61,7 @@ mod platform { } #[cfg(target_os = "ios")] mod platform { + pub use iced_ios::widget::*; } pub use platform::*; From 4b8401b88db141774abc5a476575bcb89a95ee33 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 28 May 2020 22:31:50 -0700 Subject: [PATCH 08/33] Updates from rebase --- ios/src/application.rs | 6 +++--- ios/src/keyboard.rs | 4 ++++ ios/src/lib.rs | 2 ++ ios/src/mouse.rs | 4 ++++ src/application.rs | 2 +- src/lib.rs | 17 ++++------------- 6 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 ios/src/keyboard.rs create mode 100644 ios/src/mouse.rs diff --git a/ios/src/application.rs b/ios/src/application.rs index a03116f833..f1a93c27c1 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,4 +1,4 @@ -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription}; +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, Event as IphoneEvent}; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopProxy}, @@ -100,7 +100,7 @@ pub trait Application: Sized { Self: 'static + Sized, { - let event_loop = EventLoop::with_user_event(); + let event_loop : EventLoop = EventLoop::with_user_event(); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -142,7 +142,7 @@ pub trait Application: Sized { //let root_view: UIView = UIView(window.ui_view() as id); //add_views(root_view); - event_loop.run(move |event: winit::event::Event<()>, _, control_flow| match event { + event_loop.run(move |event: winit::event::Event, _, control_flow| match event { event::Event::MainEventsCleared => { window.request_redraw(); } diff --git a/ios/src/keyboard.rs b/ios/src/keyboard.rs new file mode 100644 index 0000000000..26137171c9 --- /dev/null +++ b/ios/src/keyboard.rs @@ -0,0 +1,4 @@ + +pub type Event = (); +pub type KeyCode = (); +pub type ModifiersState = (); diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 09d826c453..129efb52e8 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -4,6 +4,8 @@ pub use iced_futures::{executor, futures, Command}; pub use executor::Executor; mod application; pub mod widget; +pub mod keyboard; +pub mod mouse; pub use widget::{ Element, Widget, }; diff --git a/ios/src/mouse.rs b/ios/src/mouse.rs new file mode 100644 index 0000000000..b295d019ab --- /dev/null +++ b/ios/src/mouse.rs @@ -0,0 +1,4 @@ +pub type Button = (); +pub type Event = (); +pub type Interaction = (); +pub type ScrollDelta = (); diff --git a/src/application.rs b/src/application.rs index 5e691fa778..9be93c88b2 100644 --- a/src/application.rs +++ b/src/application.rs @@ -232,7 +232,7 @@ where } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl crate::runtime::Application for Instance where A: Application, diff --git a/src/lib.rs b/src/lib.rs index 07122fb1b8..c8185c49fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,28 +199,19 @@ pub mod time; #[cfg(all( not(target_arch = "wasm32"), - not(target_arch = "ios"), + not(target_os = "ios"), not(feature = "glow"), feature = "wgpu" ))] use iced_winit as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(target_arch = "ios"), - feature = "glow") - )] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "glow"))] use iced_glutin as runtime; -#[cfg(all( - not(target_arch = "wasm32"), - not(target_arch = "ios"), - not(feature = "glow"), - feature = "wgpu" -))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), not(feature = "glow"), feature = "wgpu"))] use iced_wgpu as renderer; -#[cfg(all(not(target_arch = "wasm32"), feature = "glow"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios"), feature = "glow"))] use iced_glow as renderer; #[cfg(target_arch = "wasm32")] From 34ce2073db947f8a64613eca8948e1a434c39783 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sat, 13 Jun 2020 13:00:47 -0700 Subject: [PATCH 09/33] minor updates --- ios/Cargo.toml | 4 +++- ios/src/application.rs | 6 +++--- ios/src/lib.rs | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 290217989e..7eeaac8e41 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -11,7 +11,9 @@ twox-hash = "1.5" raw-window-handle = "0.3" unicode-segmentation = "1.6" winit = "0.22" -uikit-sys = { path = "../../../rust-ios/uikit-sys/" } + +uikit-sys = { git = "https://github.com/simlay/uikit-sys" } +#uikit-sys = { path = "../../uikit-sys/" } [dependencies.iced_core] version = "0.2" diff --git a/ios/src/application.rs b/ios/src/application.rs index f1a93c27c1..10ca565529 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,4 +1,4 @@ -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, Event as IphoneEvent}; +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, iOSEvent}; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopProxy}, @@ -100,7 +100,7 @@ pub trait Application: Sized { Self: 'static + Sized, { - let event_loop : EventLoop = EventLoop::with_user_event(); + let event_loop : EventLoop = EventLoop::with_user_event(); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -142,7 +142,7 @@ pub trait Application: Sized { //let root_view: UIView = UIView(window.ui_view() as id); //add_views(root_view); - event_loop.run(move |event: winit::event::Event, _, control_flow| match event { + event_loop.run(move |event: winit::event::Event, _, control_flow| match event { event::Event::MainEventsCleared => { window.request_redraw(); } diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 129efb52e8..ea7ed4d59b 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -31,17 +31,18 @@ pub type Runtime = */ #[derive(PartialEq, Clone, Debug)] -pub enum Event { +#[allow(non_camel_case_types)] +pub enum iOSEvent { } pub type Runtime = iced_futures::Runtime< std::collections::hash_map::DefaultHasher, - Event, Executor, Receiver, Message>; + iOSEvent, Executor, Receiver, Message>; pub type Subscription = iced_futures::Subscription< std::collections::hash_map::DefaultHasher, - Event, + iOSEvent, T, >; From 0b184153204aa7a4f2184e3d25d6cfc6c218b233 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sun, 14 Jun 2020 23:50:24 -0700 Subject: [PATCH 10/33] Added initial text widget --- examples/ios-example/src/main.rs | 5 ++ ios/Cargo.toml | 6 +- ios/src/application.rs | 59 ++++++++++++++++ ios/src/lib.rs | 5 +- ios/src/widget.rs | 2 + ios/src/widget/text.rs | 118 +++++++++++++++++++++++++++++++ 6 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 ios/src/widget/text.rs diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index 4bad7f0f0f..d90b93a473 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -7,6 +7,7 @@ use iced::{ Command, executor, Element, + Text, Container, Checkbox, }; @@ -44,11 +45,15 @@ impl Application for Simple { } fn view(&mut self) -> Element { + /* let toggle = Checkbox::new( self.enabled, "Listen to runtime events", Message::Toggled, ); toggle.into() + */ + let text = Text::new("foobar"); + text.into() } } diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 7eeaac8e41..fc311b7f96 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -8,9 +8,9 @@ edition = "2018" [dependencies] twox-hash = "1.5" -raw-window-handle = "0.3" -unicode-segmentation = "1.6" -winit = "0.22" +raw-window-handle = "^0.3" +winit = "^0.22" +objc = "*" uikit-sys = { git = "https://github.com/simlay/uikit-sys" } #uikit-sys = { path = "../../uikit-sys/" } diff --git a/ios/src/application.rs b/ios/src/application.rs index 10ca565529..3bc7f284f7 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -101,6 +101,9 @@ pub trait Application: Sized { { let event_loop : EventLoop = EventLoop::with_user_event(); + unsafe { + PROXY = Some(event_loop.create_proxy()); + } let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -161,3 +164,59 @@ pub trait Application: Sized { }) } } + +use objc::{ + declare::ClassDecl, + runtime::{ + Object, + Class, + Sel, + }, +}; + +static mut PROXY : Option> = None; +#[repr(transparent)] +struct EventHandler(pub id); +impl EventHandler { + fn new(node_id: i32) -> Self + { + let obj = unsafe { + let obj: id = objc::msg_send![Self::class(), alloc]; + let obj: id = objc::msg_send![obj, init]; + (*obj).set_ivar::("node_id", node_id); + obj + }; + Self(obj) + } + extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) + { + unsafe { + if let Some(ref proxy) = PROXY { + let node_id = *this.get_ivar::("node_id"); + let _ = proxy.send_event(iOSEvent::Test); + } + } + } + + fn class() -> &'static Class + { + let cls_name = "RustEventHandler"; + match Class::get(cls_name) { + Some(cls) => cls, + None => { + let superclass = objc::class!(NSObject); + let mut decl = ClassDecl::new(cls_name, superclass).unwrap(); + unsafe { + decl.add_method( + objc::sel!(sendEvent), + Self::event as extern "C" fn(&Object, Sel), + ); + } + decl.add_ivar::("node_id"); + decl.register() + } + } + } +} + + diff --git a/ios/src/lib.rs b/ios/src/lib.rs index ea7ed4d59b..416508fd2d 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -1,3 +1,4 @@ +#[macro_use] extern crate objc; pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] @@ -7,7 +8,7 @@ pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ - Element, Widget, + Element, Widget, Text, }; mod proxy; mod bus; @@ -15,6 +16,7 @@ use proxy::Proxy; mod layout; pub use layout::Layout; pub use application::Application; + /* //! Run commands and subscriptions. use crate::{Event, Hasher}; @@ -33,6 +35,7 @@ pub type Runtime = #[derive(PartialEq, Clone, Debug)] #[allow(non_camel_case_types)] pub enum iOSEvent { + Test, } pub type Runtime = diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 1ad0eef87f..ddc0648856 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -3,9 +3,11 @@ use crate::{ }; pub mod container; pub mod checkbox; +pub mod text; pub use container::Container; pub use checkbox::Checkbox; +pub use text::Text; pub trait Widget { fn draw( &self, diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs new file mode 100644 index 0000000000..b253659699 --- /dev/null +++ b/ios/src/widget/text.rs @@ -0,0 +1,118 @@ + +use crate::{ +// css, Bus, Color, Css, + Element, Font, HorizontalAlignment, Length, Color, + VerticalAlignment, Widget, +}; + +/// A paragraph of text. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Text; +/// +/// Text::new("I <3 iced!") +/// .size(40); +/// ``` +#[derive(Debug, Clone)] +pub struct Text { + content: String, + size: Option, + color: Option, + font: Font, + width: Length, + height: Length, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, +} + +impl Text { + /// Create a new fragment of [`Text`] with the given contents. + /// + /// [`Text`]: struct.Text.html + pub fn new>(label: T) -> Self { + Text { + content: label.into(), + size: None, + color: None, + font: Font::Default, + width: Length::Shrink, + height: Length::Shrink, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Top, + } + } + + /// Sets the size of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the [`Color`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Color`]: ../../struct.Color.html + pub fn color>(mut self, color: C) -> Self { + self.color = Some(color.into()); + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Font`]: ../../struct.Font.html + pub fn font(mut self, font: Font) -> Self { + self.font = font; + self + } + + /// Sets the width of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the [`HorizontalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html + pub fn horizontal_alignment( + mut self, + alignment: HorizontalAlignment, + ) -> Self { + self.horizontal_alignment = alignment; + self + } + + /// Sets the [`VerticalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`VerticalAlignment`]: enum.VerticalAlignment.html + pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self { + self.vertical_alignment = alignment; + self + } +} + +impl<'a, Message> Widget for Text { +} + +impl<'a, Message> From for Element<'a, Message> { + fn from(text: Text) -> Element<'a, Message> { + Element::new(text) + } +} From 341338e6cd136c8c8b87b759323b9fb08a4aee83 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Wed, 17 Jun 2020 10:44:16 -0700 Subject: [PATCH 11/33] Added initial text and text input widgets --- examples/ios-example/src/main.rs | 31 ++-- ios/Cargo.toml | 4 + ios/src/application.rs | 70 ++++----- ios/src/bus.rs | 56 ------- ios/src/lib.rs | 30 +++- ios/src/widget.rs | 6 +- ios/src/widget/text.rs | 57 ++++++- ios/src/widget/text_input.rs | 251 +++++++++++++++++++++++++++++++ 8 files changed, 393 insertions(+), 112 deletions(-) delete mode 100644 ios/src/bus.rs create mode 100644 ios/src/widget/text_input.rs diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index d90b93a473..0951616a81 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -2,13 +2,16 @@ use iced::{ //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, //Sandbox, //Scrollable, Settings, Slider, Space, Text, TextInput, - Application, + Sandbox, Settings, Command, executor, Element, Text, + TextInput, + text_input, Container, + Color, Checkbox, }; pub fn main() { @@ -20,28 +23,27 @@ pub fn main() { #[derive(Debug, Default)] pub struct Simple { enabled: bool, + text_state: text_input::State, } #[derive(Debug, Clone)] pub enum Message { //EventOccurred(iced_native::Event), Toggled(bool), + TextUpdated(String), } -impl Application for Simple { - type Executor = executor::Default; +impl Sandbox for Simple { type Message = Message; - type Flags = (); - fn new(flags: Self::Flags) -> (Self, Command) { - (Self::default(), Command::none()) + fn new() -> Self { + Self::default() } fn title(&self) -> String { String::from("Events - Iced") } - fn update(&mut self, message: Message) -> Command { - Command::none() + fn update(&mut self, _message: Message) { } fn view(&mut self) -> Element { @@ -52,8 +54,17 @@ impl Application for Simple { Message::Toggled, ); toggle.into() - */ - let text = Text::new("foobar"); + + let text = Text::new("foobar").color(Color::BLACK); text.into() + */ + + let text_field = TextInput::new( + &mut self.text_state, + "", + "", + |s| { Message::TextUpdated(s) } + ); + text_field.into() } } diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 76dfa3772e..2f0f87d1ed 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -18,6 +18,10 @@ uikit-sys = { git = "https://github.com/simlay/uikit-sys" } version = "0.2" path = "../core" +[dependencies.iced_style] +version = "0.1" +path = "../style" + [dependencies.iced_futures] version = "0.1" path = "../futures" diff --git a/ios/src/application.rs b/ios/src/application.rs index a82e35caf9..48d55d79ea 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,14 +1,11 @@ -<<<<<<< HEAD use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, iOSEvent}; -======= -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, Event as IphoneEvent}; ->>>>>>> 781da4d53cd84f58dab486eff1bf4f46fdf3347c use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopProxy}, platform::ios::{EventLoopExtIOS, WindowBuilderExtIOS, WindowExtIOS}, window::WindowBuilder, }; +use std::borrow::BorrowMut; use uikit_sys::{ self, //CGRect, @@ -103,22 +100,15 @@ pub trait Application: Sized { where Self: 'static + Sized, { - -<<<<<<< HEAD let event_loop : EventLoop = EventLoop::with_user_event(); - unsafe { - PROXY = Some(event_loop.create_proxy()); - } -======= - let event_loop : EventLoop = EventLoop::with_user_event(); ->>>>>>> 781da4d53cd84f58dab486eff1bf4f46fdf3347c + EventHandler::init(event_loop.create_proxy()); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); Runtime::new(executor, Proxy::new(event_loop.create_proxy())) }; - let (app, command) = runtime.enter(|| Self::new(flags)); + let (mut app, command) = runtime.enter(|| Self::new(flags)); let title = app.title(); @@ -144,30 +134,35 @@ pub trait Application: Sized { let root_view: UIView = UIView(window.ui_view() as id); unsafe { - let color = UIColor::alloc(); - let background = UIColor( - color.initWithRed_green_blue_alpha_(0.1, 1.0, 2.0, 2.0), - ); + let background = UIColor(UIColor::greenColor()); + //let background = UIColor(UIColor::whiteColor()); root_view.setBackgroundColor_(background.0); } - //let root_view: UIView = UIView(window.ui_view() as id); - //add_views(root_view); - event_loop.run(move |event: winit::event::Event, _, control_flow| match event { - event::Event::MainEventsCleared => { - window.request_redraw(); - } - event::Event::UserEvent(_message) => { - //external_messages.push(message); - } - event::Event::RedrawRequested(_) => {} - event::Event::WindowEvent { - event: _window_event, - .. - } => { - } - _ => { - *control_flow = ControlFlow::Wait; + event_loop.run(move |event: winit::event::Event, _, control_flow| { + crate::ios_log(format!("NEW EVENT: {:?}", event)); + match event { + event::Event::MainEventsCleared => { + window.request_redraw(); + } + event::Event::UserEvent(message) => { + println!("GOT NEW USER EVENT: {:?}", message); + //external_messages.push(message); + } + event::Event::RedrawRequested(_) => {} + event::Event::WindowEvent { + event: _window_event, + .. + } => { + } + event::Event::NewEvents(event::StartCause::Init) => { + let root_view: UIView = UIView(window.ui_view() as id); + let element = app.borrow_mut().view(); + element.widget.draw(root_view); + } + _ => { + *control_flow = ControlFlow::Wait; + } } }) } @@ -182,10 +177,15 @@ use objc::{ }, }; -static mut PROXY : Option> = None; #[repr(transparent)] struct EventHandler(pub id); +static mut PROXY : Option> = None; impl EventHandler { + fn init(proxy: EventLoopProxy) { + unsafe { + PROXY = Some(proxy); + } + } fn new(node_id: i32) -> Self { let obj = unsafe { diff --git a/ios/src/bus.rs b/ios/src/bus.rs deleted file mode 100644 index 351b159d4b..0000000000 --- a/ios/src/bus.rs +++ /dev/null @@ -1,56 +0,0 @@ - -use iced_futures::futures::channel::mpsc; -use std::rc::Rc; - -/// A publisher of messages. -/// -/// It can be used to route messages back to the [`Application`]. -/// -/// [`Application`]: trait.Application.html -#[allow(missing_debug_implementations)] -pub struct Bus { - publish: Rc ()>>, -} - -impl Clone for Bus { - fn clone(&self) -> Self { - Bus { - publish: self.publish.clone(), - } - } -} - -impl Bus -where - Message: 'static, -{ - pub(crate) fn new(publish: mpsc::UnboundedSender) -> Self { - Self { - publish: Rc::new(Box::new(move |message| { - publish.unbounded_send(message).expect("Send message"); - })), - } - } - - /// Publishes a new message for the [`Application`]. - /// - /// [`Application`]: trait.Application.html - pub fn publish(&self, message: Message) { - (self.publish)(message) - } - - /// Creates a new [`Bus`] that applies the given function to the messages - /// before publishing. - /// - /// [`Bus`]: struct.Bus.html - pub fn map(&self, mapper: Rc Message>>) -> Bus - where - B: 'static, - { - let publish = self.publish.clone(); - - Bus { - publish: Rc::new(Box::new(move |message| publish(mapper(message)))), - } - } -} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 5b134dfc5e..684bca6381 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -1,7 +1,4 @@ -<<<<<<< HEAD #[macro_use] extern crate objc; -======= ->>>>>>> 781da4d53cd84f58dab486eff1bf4f46fdf3347c pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] @@ -11,10 +8,9 @@ pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ - Element, Widget, Text, + Element, Widget, Text, TextInput }; mod proxy; -mod bus; use proxy::Proxy; mod layout; pub use layout::Layout; @@ -48,7 +44,6 @@ pub type Runtime = pub type Subscription = iced_futures::Subscription< std::collections::hash_map::DefaultHasher, iOSEvent, - Event, Executor, Receiver, Message>; T, >; @@ -56,3 +51,26 @@ pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; + +pub fn ios_log(s: String) { + use uikit_sys::{ + NSLog, + NSString, + NSString_NSStringExtensionMethods, + }; + use std::convert::TryInto; + use std::ffi::CString; + unsafe { + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(s.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + s.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + NSLog(text.0); + } + +} diff --git a/ios/src/widget.rs b/ios/src/widget.rs index ddc0648856..ab35ee5fcd 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -4,14 +4,18 @@ use crate::{ pub mod container; pub mod checkbox; pub mod text; +pub mod text_input; pub use container::Container; pub use checkbox::Checkbox; pub use text::Text; +pub use text_input::TextInput; +use uikit_sys::UIView; + pub trait Widget { fn draw( &self, - _layout: Layout<'_>, + _parent: UIView, ) { } } diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index b253659699..293ac77b52 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -1,8 +1,12 @@ - use crate::{ -// css, Bus, Color, Css, - Element, Font, HorizontalAlignment, Length, Color, - VerticalAlignment, Widget, + Color, + // css, Bus, Color, Css, + Element, + Font, + HorizontalAlignment, + Length, + VerticalAlignment, + Widget, }; /// A paragraph of text. @@ -107,8 +111,53 @@ impl Text { self } } +use std::convert::TryInto; +use std::ffi::CString; +use uikit_sys::{ + CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, + NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, + UIView_UIViewGeometry, UIView_UIViewHierarchy, +}; impl<'a, Message> Widget for Text { + fn draw(&self, parent: UIView) { + unsafe { + let label = UILabel::alloc(); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.init(); + label.setText_(text.0); + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 20.0, + width: 500.0, + }, + }; + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(100.0); + label.setFrame_(rect); + if let Some(color) = self.color { + let background = + UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + )); + label.setTextColor_(background.0) + } + label.setFrame_(rect); + parent.addSubview_(label.0); + }; + } } impl<'a, Message> From for Element<'a, Message> { diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs new file mode 100644 index 0000000000..d567f3791a --- /dev/null +++ b/ios/src/widget/text_input.rs @@ -0,0 +1,251 @@ + +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html +use crate::{Element, Length, Widget}; + +pub use iced_style::text_input::{Style, StyleSheet}; + +use std::{rc::Rc, u32}; + +/// A field that can be filled with text. +/// +/// # Example +/// ``` +/// # use iced_web::{text_input, TextInput}; +/// # +/// enum Message { +/// TextInputChanged(String), +/// } +/// +/// let mut state = text_input::State::new(); +/// let value = "Some text"; +/// +/// let input = TextInput::new( +/// &mut state, +/// "This is the placeholder...", +/// value, +/// Message::TextInputChanged, +/// ); +/// ``` +#[allow(missing_debug_implementations)] +pub struct TextInput<'a, Message> { + _state: &'a mut State, + placeholder: String, + value: String, + is_secure: bool, + width: Length, + max_width: u32, + padding: u16, + size: Option, + on_change: Rc Message>>, + on_submit: Option, + style_sheet: Box, +} + +impl<'a, Message> TextInput<'a, Message> { + /// Creates a new [`TextInput`]. + /// + /// It expects: + /// - some [`State`] + /// - a placeholder + /// - the current value + /// - a function that produces a message when the [`TextInput`] changes + /// + /// [`TextInput`]: struct.TextInput.html + /// [`State`]: struct.State.html + pub fn new( + state: &'a mut State, + placeholder: &str, + value: &str, + on_change: F, + ) -> Self + where + F: 'static + Fn(String) -> Message, + { + Self { + _state: state, + placeholder: String::from(placeholder), + value: String::from(value), + is_secure: false, + width: Length::Fill, + max_width: u32::MAX, + padding: 0, + size: None, + on_change: Rc::new(Box::new(on_change)), + on_submit: None, + style_sheet: Default::default(), + } + } + + /// Converts the [`TextInput`] into a secure password input. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn password(mut self) -> Self { + self.is_secure = true; + self + } + + /// Sets the width of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the maximum width of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the padding of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the text size of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the message that should be produced when the [`TextInput`] is + /// focused and the enter key is pressed. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn on_submit(mut self, message: Message) -> Self { + self.on_submit = Some(message); + self + } + + /// Sets the style of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style_sheet = style.into(); + self + } +} + +use std::convert::TryInto; +use std::ffi::CString; +use uikit_sys::{ + id, CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, + NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, + UIView_UIViewGeometry, UIView_UIViewHierarchy, +}; + +impl<'a, Message> Widget for TextInput<'a, Message> +where + Message: 'static + Clone, +{ + fn draw(&self, parent: UIView) { + use uikit_sys::{ + UITextView, + IUITextView, + }; + let input_rect = CGRect { + origin: CGPoint { + x: 10.0, + y: 50.0 + }, + size: CGSize { + width: 200.0, + height: 200.0, + } + }; + unsafe { + let input = { + let foo = UITextView( + UITextView::alloc().initWithFrame_textContainer_( + input_rect, + 0 as id, + )); + foo + }; + parent.addSubview_(input.0); + } + /* + unsafe { + let label = UILabel::alloc(); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.init(); + label.setText_(text.0); + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 20.0, + width: 500.0, + }, + }; + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(100.0); + label.setFrame_(rect); + if let Some(color) = self.color { + let background = + UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + )); + label.setTextColor_(background.0) + } + label.setFrame_(rect); + parent.addSubview_(label.0); + }; + */ + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static + Clone, +{ + fn from(text_input: TextInput<'a, Message>) -> Element<'a, Message> { + Element::new(text_input) + } +} + +/// The state of a [`TextInput`]. +/// +/// [`TextInput`]: struct.TextInput.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State; + +impl State { + /// Creates a new [`State`], representing an unfocused [`TextInput`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + Self::default() + } + + /// Creates a new [`State`], representing a focused [`TextInput`]. + /// + /// [`State`]: struct.State.html + pub fn focused() -> Self { + // TODO + Self::default() + } +} From 4f615d49554792d6160fa72f76e1347ea06c4e53 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Wed, 17 Jun 2020 15:44:47 -0700 Subject: [PATCH 12/33] Minor updates --- ios/src/application.rs | 2 +- ios/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/src/application.rs b/ios/src/application.rs index 48d55d79ea..fc2d33ed17 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -201,7 +201,7 @@ impl EventHandler { unsafe { if let Some(ref proxy) = PROXY { let node_id = *this.get_ivar::("node_id"); - let _ = proxy.send_event(iOSEvent::Test); + let _ = proxy.send_event(iOSEvent::TextInput); } } } diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 684bca6381..6ad14332b4 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -33,7 +33,7 @@ pub type Runtime = #[derive(PartialEq, Clone, Debug)] #[allow(non_camel_case_types)] pub enum iOSEvent { - Test, + TextInput, } pub type Runtime = From 65aad316e23da2a3b4bd0550ac62a9ed7c877e5a Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Wed, 17 Jun 2020 21:55:04 -0700 Subject: [PATCH 13/33] Added more structure to the eventing system --- ios/src/application.rs | 66 ++--------------------------- ios/src/event.rs | 80 ++++++++++++++++++++++++++++++++++++ ios/src/lib.rs | 12 ++---- ios/src/widget.rs | 12 +++++- ios/src/widget/text_input.rs | 12 +++++- 5 files changed, 109 insertions(+), 73 deletions(-) create mode 100644 ios/src/event.rs diff --git a/ios/src/application.rs b/ios/src/application.rs index fc2d33ed17..5adeeaa214 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,4 +1,4 @@ -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, iOSEvent}; +use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, event::{EventHandler, WidgetEvent}}; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopProxy}, @@ -100,7 +100,7 @@ pub trait Application: Sized { where Self: 'static + Sized, { - let event_loop : EventLoop = EventLoop::with_user_event(); + let event_loop : EventLoop = EventLoop::with_user_event(); EventHandler::init(event_loop.create_proxy()); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -139,7 +139,7 @@ pub trait Application: Sized { root_view.setBackgroundColor_(background.0); } - event_loop.run(move |event: winit::event::Event, _, control_flow| { + event_loop.run(move |event: winit::event::Event, _, control_flow| { crate::ios_log(format!("NEW EVENT: {:?}", event)); match event { event::Event::MainEventsCleared => { @@ -167,63 +167,3 @@ pub trait Application: Sized { }) } } - -use objc::{ - declare::ClassDecl, - runtime::{ - Object, - Class, - Sel, - }, -}; - -#[repr(transparent)] -struct EventHandler(pub id); -static mut PROXY : Option> = None; -impl EventHandler { - fn init(proxy: EventLoopProxy) { - unsafe { - PROXY = Some(proxy); - } - } - fn new(node_id: i32) -> Self - { - let obj = unsafe { - let obj: id = objc::msg_send![Self::class(), alloc]; - let obj: id = objc::msg_send![obj, init]; - (*obj).set_ivar::("node_id", node_id); - obj - }; - Self(obj) - } - extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) - { - unsafe { - if let Some(ref proxy) = PROXY { - let node_id = *this.get_ivar::("node_id"); - let _ = proxy.send_event(iOSEvent::TextInput); - } - } - } - - fn class() -> &'static Class - { - let cls_name = "RustEventHandler"; - match Class::get(cls_name) { - Some(cls) => cls, - None => { - let superclass = objc::class!(NSObject); - let mut decl = ClassDecl::new(cls_name, superclass).unwrap(); - unsafe { - decl.add_method( - objc::sel!(sendEvent), - Self::event as extern "C" fn(&Object, Sel), - ); - } - decl.add_ivar::("node_id"); - decl.register() - } - } - } -} - diff --git a/ios/src/event.rs b/ios/src/event.rs new file mode 100644 index 0000000000..07e866d281 --- /dev/null +++ b/ios/src/event.rs @@ -0,0 +1,80 @@ +use winit::event_loop::EventLoopProxy; +use objc::{ + declare::ClassDecl, + runtime::{ + Object, + Class, + Sel, + }, +}; + +use uikit_sys::{ + id, +}; + + +#[derive(PartialEq, Clone, Debug)] +pub struct WidgetEvent { + widget_id: u64, +} + +#[derive(Debug)] +pub struct EventHandler{ + pub id: id, + pub widget_id: u64, +} +use std::sync::atomic::{AtomicU64, Ordering}; +static mut PROXY : Option> = None; +static mut COUNTER: Option = None; +impl EventHandler { + pub fn init(proxy: EventLoopProxy) { + unsafe { + COUNTER = Some(AtomicU64::new(0)); + PROXY = Some(proxy); + } + } + pub fn new() -> Self + { + let mut widget_id = 0; + let obj = unsafe { + let obj: id = objc::msg_send![Self::class(), alloc]; + let obj: id = objc::msg_send![obj, init]; + + if let Some(counter) = &COUNTER { + widget_id = counter.fetch_add(0, Ordering::Relaxed); + (*obj).set_ivar::("widget_id", widget_id); + } + obj + }; + Self{id: obj, widget_id} + } + extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) + { + unsafe { + if let Some(ref proxy) = PROXY { + let widget_id = *this.get_ivar::("widget_id"); + let _ = proxy.send_event(WidgetEvent { widget_id } ); + } + } + } + + fn class() -> &'static Class + { + let cls_name = "RustEventHandler"; + match Class::get(cls_name) { + Some(cls) => cls, + None => { + let superclass = objc::class!(NSObject); + let mut decl = ClassDecl::new(cls_name, superclass).unwrap(); + unsafe { + decl.add_method( + objc::sel!(sendEvent), + Self::event as extern "C" fn(&Object, Sel), + ); + } + decl.add_ivar::("widget_id"); + decl.register() + } + } + } +} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 6ad14332b4..dae097fa12 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -4,12 +4,14 @@ pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] pub use executor::Executor; mod application; +mod event; pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ Element, Widget, Text, TextInput }; +use event::WidgetEvent; mod proxy; use proxy::Proxy; mod layout; @@ -30,20 +32,14 @@ pub type Runtime = iced_futures::Runtime; */ -#[derive(PartialEq, Clone, Debug)] -#[allow(non_camel_case_types)] -pub enum iOSEvent { - TextInput, -} - pub type Runtime = iced_futures::Runtime< std::collections::hash_map::DefaultHasher, - iOSEvent, Executor, Receiver, Message>; + WidgetEvent, Executor, Receiver, Message>; pub type Subscription = iced_futures::Subscription< std::collections::hash_map::DefaultHasher, - iOSEvent, + WidgetEvent, T, >; diff --git a/ios/src/widget.rs b/ios/src/widget.rs index ab35ee5fcd..982e0ba99d 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,5 +1,6 @@ use crate::{ Layout, + event::WidgetEvent, }; pub mod container; pub mod checkbox; @@ -34,5 +35,14 @@ impl<'a, Message> Element<'a, Message> { widget: Box::new(widget), } } + fn on_event( + &mut self, + _event: WidgetEvent, + _layout: Layout<'_>, + //_cursor_position: Point, + _messages: &mut Vec, + //_renderer: &Renderer, + //_clipboard: Option<&dyn Clipboard>, + ) { + } } - diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index d567f3791a..85428fddda 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -5,7 +5,7 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::{Element, Length, Widget}; +use crate::{Element, Length, Widget, event::EventHandler}; pub use iced_style::text_input::{Style, StyleSheet}; @@ -155,6 +155,7 @@ where use uikit_sys::{ UITextView, IUITextView, + IUIControl, }; let input_rect = CGRect { origin: CGPoint { @@ -166,6 +167,7 @@ where height: 200.0, } }; + let on_change = EventHandler::new(); unsafe { let input = { let foo = UITextView( @@ -175,6 +177,14 @@ where )); foo }; + /* + + input.addTarget_action_forControlEvents_( + on_change.id, + sel!(sendEvent), + uikit_sys::UIControlEvents_UIControlEventValueChanged, + ); + */ parent.addSubview_(input.0); } /* From 46ed1c2af4d3912893422e3c2b50f52915f4181f Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Mon, 29 Jun 2020 12:37:59 -0700 Subject: [PATCH 14/33] Added hashing logic --- examples/ios-example/Cargo.toml | 6 +- examples/ios-example/src/main.rs | 13 ++- ios/Cargo.toml | 3 +- ios/src/application.rs | 78 +++++++++++++----- ios/src/event.rs | 11 ++- ios/src/lib.rs | 27 +------ ios/src/proxy.rs | 58 ------------- ios/src/widget.rs | 26 +++--- ios/src/widget/checkbox.rs | 2 + ios/src/widget/container.rs | 3 +- ios/src/widget/text.rs | 14 +++- ios/src/widget/text_input.rs | 134 +++++++++++++++++++++---------- 12 files changed, 204 insertions(+), 171 deletions(-) delete mode 100644 ios/src/proxy.rs diff --git a/examples/ios-example/Cargo.toml b/examples/ios-example/Cargo.toml index 1e3d9a0ba2..da90c1bafd 100644 --- a/examples/ios-example/Cargo.toml +++ b/examples/ios-example/Cargo.toml @@ -21,4 +21,8 @@ cargo-bundle's support for bundling crate examples. [dependencies] iced = { path = "../..", features = ["image", "debug"] } -env_logger = "0.7" +log = "0.4" +pretty_env_logger = "0.3" +color-backtrace = { version = "0.4.2", features = ["failure-bt"] } + +#env_logger = "0.7" diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index 0951616a81..b13f52e380 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -1,3 +1,4 @@ +#[macro_use] extern crate log; use iced::{ //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, @@ -15,7 +16,13 @@ use iced::{ Checkbox, }; pub fn main() { - env_logger::init(); + color_backtrace::install_with_settings( + color_backtrace::Settings::new().verbosity(color_backtrace::Verbosity::Full), + ); + std::env::set_var("RUST_LOG", "DEBUG"); + std::env::set_var("RUST_BACKTRACE", "full"); + pretty_env_logger::init(); + //env_logger::init(); Simple::run(Settings::default()) } @@ -43,10 +50,12 @@ impl Sandbox for Simple { String::from("Events - Iced") } - fn update(&mut self, _message: Message) { + fn update(&mut self, message: Message) { + debug!("GOT NEW MESSAGE: {:?}", message); } fn view(&mut self) -> Element { + debug!("RERUNNING VIEW"); /* let toggle = Checkbox::new( self.enabled, diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 2f0f87d1ed..89a46ea70d 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -10,8 +10,9 @@ edition = "2018" twox-hash = "1.5" raw-window-handle = "^0.3" winit = "^0.22" -objc = "*" +objc = "0.2.7" uikit-sys = { git = "https://github.com/simlay/uikit-sys" } +log = "0.4" #uikit-sys = { path = "../../uikit-sys/" } [dependencies.iced_core] diff --git a/ios/src/application.rs b/ios/src/application.rs index 5adeeaa214..9b725cc048 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,11 +1,14 @@ -use crate::{Runtime, Command, Element, Executor, Proxy, Subscription, event::{EventHandler, WidgetEvent}}; +use std::hash::Hasher; +use crate::{Runtime, Command, Element, Executor, Subscription, event::{EventHandler, WidgetEvent}}; use winit::{ - event::{self, WindowEvent}, - event_loop::{ControlFlow, EventLoop, EventLoopProxy}, - platform::ios::{EventLoopExtIOS, WindowBuilderExtIOS, WindowExtIOS}, + event, + event_loop::{ControlFlow, EventLoop}, + platform::ios::{WindowExtIOS}, window::WindowBuilder, }; -use std::borrow::BorrowMut; + +use std::cell::RefCell; +use std::rc::Rc; use uikit_sys::{ self, //CGRect, @@ -101,14 +104,18 @@ pub trait Application: Sized { Self: 'static + Sized, { let event_loop : EventLoop = EventLoop::with_user_event(); - EventHandler::init(event_loop.create_proxy()); + let proxy = event_loop.create_proxy(); + EventHandler::init(proxy.clone()); + let (sender, _receiver) = iced_futures::futures::channel::mpsc::unbounded(); + let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); - Runtime::new(executor, Proxy::new(event_loop.create_proxy())) + Runtime::new(executor, sender) }; let (mut app, command) = runtime.enter(|| Self::new(flags)); + runtime.spawn(command); let title = app.title(); @@ -130,40 +137,71 @@ pub trait Application: Sized { window_builder.build(&event_loop).expect("Open window") }; - window.request_redraw(); - let root_view: UIView = UIView(window.ui_view() as id); unsafe { let background = UIColor(UIColor::greenColor()); //let background = UIColor(UIColor::whiteColor()); root_view.setBackgroundColor_(background.0); } + //let app = app.borrow_mut(); + //let mut current_element: Rc>> = Rc::new(RefCell::new(&mut app.view())); + window.request_redraw(); + //proxy.send_event(WidgetEvent {widget_id: 0} ); + let mut cached_hash : u64 = 0; + let mut current_element: Option> = None; event_loop.run(move |event: winit::event::Event, _, control_flow| { - crate::ios_log(format!("NEW EVENT: {:?}", event)); + //let new_title = application.borrow().title(); + //debug!("NEW EVENT: {:?}", event); + let mut messages : Vec = Vec::new(); match event { event::Event::MainEventsCleared => { - window.request_redraw(); } - event::Event::UserEvent(message) => { - println!("GOT NEW USER EVENT: {:?}", message); - //external_messages.push(message); + event::Event::UserEvent(widget_event) => { + info!("GOT NEW USER EVENT: {:?}", widget_event); + + /* + let mut element = app.view(); + element.widget.on_widget_event(widget_event, &mut messages); + + let hash = { + let mut hash = &mut crate::Hasher::default(); + element.widget.hash_layout(&mut hash); + hash.finish() + }; + if hash != cached_hash { + cached_hash = hash; + element.widget.draw(root_view); + } + */ } event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { event: _window_event, .. - } => { - } - event::Event::NewEvents(event::StartCause::Init) => { + } => { } event::Event::NewEvents(event::StartCause::Init) => { + /* let root_view: UIView = UIView(window.ui_view() as id); - let element = app.borrow_mut().view(); - element.widget.draw(root_view); + app.view().widget.draw(root_view); + */ } _ => { *control_flow = ControlFlow::Wait; } } - }) + for message in messages { + + let (command, subscription) = runtime.enter(|| { + let command = app.update(message); + let subscription = app.subscription(); + + (command, subscription) + }); + + + runtime.spawn(command); + runtime.track(subscription); + } + }); } } diff --git a/ios/src/event.rs b/ios/src/event.rs index 07e866d281..0ecbb06863 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -8,22 +8,21 @@ use objc::{ }, }; -use uikit_sys::{ - id, -}; +use uikit_sys::id; +use std::sync::atomic::{AtomicU64, Ordering}; #[derive(PartialEq, Clone, Debug)] pub struct WidgetEvent { - widget_id: u64, + pub widget_id: u64, } #[derive(Debug)] -pub struct EventHandler{ +pub struct EventHandler { pub id: id, pub widget_id: u64, } -use std::sync::atomic::{AtomicU64, Ordering}; + static mut PROXY : Option> = None; static mut COUNTER: Option = None; impl EventHandler { diff --git a/ios/src/lib.rs b/ios/src/lib.rs index dae097fa12..8fc0d25830 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -1,4 +1,5 @@ #[macro_use] extern crate objc; +#[macro_use] extern crate log; pub use iced_futures::{executor, futures, Command}; #[doc(no_inline)] @@ -12,8 +13,6 @@ pub use widget::{ Element, Widget, Text, TextInput }; use event::WidgetEvent; -mod proxy; -use proxy::Proxy; mod layout; pub use layout::Layout; pub use application::Application; @@ -32,6 +31,7 @@ pub type Runtime = iced_futures::Runtime; */ +pub type Hasher = std::collections::hash_map::DefaultHasher; pub type Runtime = iced_futures::Runtime< std::collections::hash_map::DefaultHasher, @@ -47,26 +47,3 @@ pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; - -pub fn ios_log(s: String) { - use uikit_sys::{ - NSLog, - NSString, - NSString_NSStringExtensionMethods, - }; - use std::convert::TryInto; - use std::ffi::CString; - unsafe { - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(s.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - s.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - NSLog(text.0); - } - -} diff --git a/ios/src/proxy.rs b/ios/src/proxy.rs deleted file mode 100644 index 2683688936..0000000000 --- a/ios/src/proxy.rs +++ /dev/null @@ -1,58 +0,0 @@ -use iced_futures::futures::{ - channel::mpsc, - task::{Context, Poll}, - Sink, -}; -use std::pin::Pin; - -pub struct Proxy { - raw: winit::event_loop::EventLoopProxy, -} - -impl Clone for Proxy { - fn clone(&self) -> Self { - Self { - raw: self.raw.clone(), - } - } -} - -impl Proxy { - pub fn new(raw: winit::event_loop::EventLoopProxy) -> Self { - Self { raw } - } -} - -impl Sink for Proxy { - type Error = mpsc::SendError; - - fn poll_ready( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - - fn start_send( - self: Pin<&mut Self>, - message: Message, - ) -> Result<(), Self::Error> { - let _ = self.raw.send_event(message); - - Ok(()) - } - - fn poll_flush( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_close( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } -} diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 982e0ba99d..53799317c7 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,6 +1,7 @@ use crate::{ - Layout, + //Layout, event::WidgetEvent, + Hasher, }; pub mod container; pub mod checkbox; @@ -15,10 +16,21 @@ use uikit_sys::UIView; pub trait Widget { fn draw( - &self, + &mut self, _parent: UIView, ) { } + fn hash_layout(&self, state: &mut Hasher); + fn on_widget_event( + &mut self, + _event: WidgetEvent, + //_layout: Layout<'_>, + //_cursor_position: Point, + _messages: &mut Vec, + //_renderer: &Renderer, + //_clipboard: Option<&dyn Clipboard>, + ) { + } } #[allow(missing_debug_implementations)] @@ -35,14 +47,4 @@ impl<'a, Message> Element<'a, Message> { widget: Box::new(widget), } } - fn on_event( - &mut self, - _event: WidgetEvent, - _layout: Layout<'_>, - //_cursor_position: Point, - _messages: &mut Vec, - //_renderer: &Renderer, - //_clipboard: Option<&dyn Clipboard>, - ) { - } } diff --git a/ios/src/widget/checkbox.rs b/ios/src/widget/checkbox.rs index ccc098a674..e4d399c5dc 100644 --- a/ios/src/widget/checkbox.rs +++ b/ios/src/widget/checkbox.rs @@ -24,6 +24,7 @@ impl Checkbox { } } } +/* impl Widget for Checkbox where Message: 'static, @@ -37,3 +38,4 @@ where Element::new(checkbox) } } +*/ diff --git a/ios/src/widget/container.rs b/ios/src/widget/container.rs index aa4bf48f21..c0b5916589 100644 --- a/ios/src/widget/container.rs +++ b/ios/src/widget/container.rs @@ -36,6 +36,7 @@ impl<'a, Message> Container<'a, Message> { } } } +/* impl<'a, Message> Widget for Container<'a, Message> where Message: 'static, @@ -49,4 +50,4 @@ where Element::new(container) } } - +*/ diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 293ac77b52..79054799b6 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -1,3 +1,4 @@ +use std::hash::Hash; use crate::{ Color, // css, Bus, Color, Css, @@ -7,6 +8,7 @@ use crate::{ Length, VerticalAlignment, Widget, + Hasher, }; /// A paragraph of text. @@ -120,7 +122,17 @@ use uikit_sys::{ }; impl<'a, Message> Widget for Text { - fn draw(&self, parent: UIView) { + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.content.hash(state); + self.size.hash(state); + self.width.hash(state); + self.height.hash(state); + } + + fn draw(&mut self, parent: UIView) { unsafe { let label = UILabel::alloc(); let text = NSString( diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 85428fddda..f748c53852 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -5,11 +5,12 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::{Element, Length, Widget, event::EventHandler}; +use crate::{Element, Length, Widget, Layout, Hasher, event::{EventHandler, WidgetEvent}}; pub use iced_style::text_input::{Style, StyleSheet}; use std::{rc::Rc, u32}; +use std::hash::Hash; /// A field that can be filled with text. /// @@ -44,6 +45,8 @@ pub struct TextInput<'a, Message> { on_change: Rc Message>>, on_submit: Option, style_sheet: Box, + widget_id: u64, + pub ui_textview: Option, } impl<'a, Message> TextInput<'a, Message> { @@ -78,6 +81,8 @@ impl<'a, Message> TextInput<'a, Message> { on_change: Rc::new(Box::new(on_change)), on_submit: None, style_sheet: Default::default(), + widget_id: 0, + ui_textview: None, } } @@ -145,18 +150,31 @@ use uikit_sys::{ id, CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, + UITextView, + IUITextView, + NSNotificationCenter, + INSNotificationCenter, + UITextViewTextDidChangeNotification, }; impl<'a, Message> Widget for TextInput<'a, Message> where Message: 'static + Clone, { - fn draw(&self, parent: UIView) { - use uikit_sys::{ - UITextView, - IUITextView, - IUIControl, - }; + fn hash_layout(&self, state: &mut Hasher) { + use std::{any::TypeId, hash::Hash}; + struct Marker; + TypeId::of::().hash(state); + + self.width.hash(state); + self.max_width.hash(state); + self.padding.hash(state); + self.size.hash(state); + } + fn draw(&mut self, parent: UIView) { + if self.ui_textview.is_some() { + return; + } let input_rect = CGRect { origin: CGPoint { x: 10.0, @@ -169,7 +187,7 @@ where }; let on_change = EventHandler::new(); unsafe { - let input = { + let ui_textview = { let foo = UITextView( UITextView::alloc().initWithFrame_textContainer_( input_rect, @@ -178,53 +196,81 @@ where foo }; /* - input.addTarget_action_forControlEvents_( on_change.id, sel!(sendEvent), uikit_sys::UIControlEvents_UIControlEventValueChanged, ); */ - parent.addSubview_(input.0); + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc + let center = NSNotificationCenter(NSNotificationCenter::defaultCenter()); + center.addObserver_selector_name_object_( + on_change.id, + sel!(sendEvent), + UITextViewTextDidChangeNotification, + ui_textview.0 + ); + parent.addSubview_(ui_textview.0); + self.ui_textview = Some(ui_textview); } + + self.widget_id = on_change.widget_id; + debug!("drow TEXT UIVIEW {:?}", self.ui_textview.is_some()); + /* + use uikit_sys::{ + UISwitch, IUISwitch, + IUIControl, + }; + let input_rect = CGRect { + origin: CGPoint { + x: 10.0, + y: 10.0 + }, + size: CGSize { + width: 200.0, + height: 200.0, + } + }; unsafe { - let label = UILabel::alloc(); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), + let switch = UISwitch( + IUISwitch::initWithFrame_( + UISwitch::alloc(), + input_rect, + ) ); - label.init(); - label.setText_(text.0); - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 20.0, - width: 500.0, - }, - }; - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(100.0); - label.setFrame_(rect); - if let Some(color) = self.color { - let background = - UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - )); - label.setTextColor_(background.0) + + /* + switch.addTarget_action_forControlEvents_( + on_change.id, + sel!(sendEvent), + uikit_sys::UIControlEvents_UIControlEventValueChanged, + ); + */ + + parent.addSubview_(switch.0); + } + */ + } + + fn on_widget_event(&mut self, widget_event: WidgetEvent, messages: &mut Vec) { + + debug!("on_widget_event TEXT UIVIEW {:?}", self.ui_textview.is_some()); + if let Some(ui_textview) = self.ui_textview { + if widget_event.widget_id == self.widget_id { + let value = unsafe { + let value = NSString(self.ui_textview.unwrap().text()); + let len = value.lengthOfBytesUsingEncoding_( + uikit_sys::NSUTF8StringEncoding, + ); + let bytes = value.UTF8String() as *const u8; + String::from_utf8(std::slice::from_raw_parts(bytes, len.try_into().unwrap()).to_vec()).unwrap() + }; + self.value = value; + + messages.push((self.on_change)(self.value.clone())); } - label.setFrame_(rect); - parent.addSubview_(label.0); - }; - */ + } } } From 21f656404977477a01f644b618d89dc2033326b1 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 2 Jul 2020 23:37:26 -0700 Subject: [PATCH 15/33] Updates to use WidgetPointers --- examples/ios-example/src/main.rs | 56 +++++++----- ios/src/application.rs | 134 +++++++++++++++------------ ios/src/layout.rs | 151 ------------------------------- ios/src/lib.rs | 6 +- ios/src/widget.rs | 42 ++++++++- ios/src/widget/container.rs | 23 ++++- ios/src/widget/text.rs | 20 +++- ios/src/widget/text_input.rs | 63 ++++++++----- src/window/settings.rs | 2 +- 9 files changed, 230 insertions(+), 267 deletions(-) delete mode 100644 ios/src/layout.rs diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index b13f52e380..be055c7137 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -16,9 +16,7 @@ use iced::{ Checkbox, }; pub fn main() { - color_backtrace::install_with_settings( - color_backtrace::Settings::new().verbosity(color_backtrace::Verbosity::Full), - ); + color_backtrace::install(); std::env::set_var("RUST_LOG", "DEBUG"); std::env::set_var("RUST_BACKTRACE", "full"); pretty_env_logger::init(); @@ -29,7 +27,8 @@ pub fn main() { #[derive(Debug, Default)] pub struct Simple { - enabled: bool, + toggle: bool, + text: String, text_state: text_input::State, } #[derive(Debug, Clone)] @@ -52,28 +51,41 @@ impl Sandbox for Simple { fn update(&mut self, message: Message) { debug!("GOT NEW MESSAGE: {:?}", message); + match message { + Message::TextUpdated(val) => { + if val.starts_with("a") { + self.toggle = true; + } + self.text = val; + }, + _ => { + }, + } } fn view(&mut self) -> Element { - debug!("RERUNNING VIEW"); - /* - let toggle = Checkbox::new( - self.enabled, - "Listen to runtime events", - Message::Toggled, - ); - toggle.into() + debug!("RERUNNING VIEW : {:#?}", self); + if self.toggle { + /* + let toggle = Checkbox::new( + self.toggle, + "Listen to runtime events", + Message::Toggled, + ); + toggle.into() + */ - let text = Text::new("foobar").color(Color::BLACK); - text.into() - */ + let text = Text::new(&self.text).color(Color::BLACK); + text.into() + } else { - let text_field = TextInput::new( - &mut self.text_state, - "", - "", - |s| { Message::TextUpdated(s) } - ); - text_field.into() + let text_field = TextInput::new( + &mut self.text_state, + "", + "", + |s| { Message::TextUpdated(s) } + ); + text_field.into() + } } } diff --git a/ios/src/application.rs b/ios/src/application.rs index 9b725cc048..e99e86eb88 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,9 +1,13 @@ +use crate::{ + event::{EventHandler, WidgetEvent}, + WidgetPointers, + Command, Element, Executor, Runtime, Subscription, +}; use std::hash::Hasher; -use crate::{Runtime, Command, Element, Executor, Subscription, event::{EventHandler, WidgetEvent}}; use winit::{ event, event_loop::{ControlFlow, EventLoop}, - platform::ios::{WindowExtIOS}, + platform::ios::WindowExtIOS, window::WindowBuilder, }; @@ -103,10 +107,11 @@ pub trait Application: Sized { where Self: 'static + Sized, { - let event_loop : EventLoop = EventLoop::with_user_event(); + let event_loop: EventLoop = EventLoop::with_user_event(); let proxy = event_loop.create_proxy(); EventHandler::init(proxy.clone()); - let (sender, _receiver) = iced_futures::futures::channel::mpsc::unbounded(); + let (sender, _receiver) = + iced_futures::futures::channel::mpsc::unbounded(); let mut runtime = { let executor = Self::Executor::new().expect("Create executor"); @@ -143,65 +148,76 @@ pub trait Application: Sized { //let background = UIColor(UIColor::whiteColor()); root_view.setBackgroundColor_(background.0); } - //let app = app.borrow_mut(); - //let mut current_element: Rc>> = Rc::new(RefCell::new(&mut app.view())); - window.request_redraw(); //proxy.send_event(WidgetEvent {widget_id: 0} ); - let mut cached_hash : u64 = 0; - let mut current_element: Option> = None; - - event_loop.run(move |event: winit::event::Event, _, control_flow| { - //let new_title = application.borrow().title(); - //debug!("NEW EVENT: {:?}", event); - let mut messages : Vec = Vec::new(); - match event { - event::Event::MainEventsCleared => { - } - event::Event::UserEvent(widget_event) => { - info!("GOT NEW USER EVENT: {:?}", widget_event); - - /* - let mut element = app.view(); - element.widget.on_widget_event(widget_event, &mut messages); - - let hash = { - let mut hash = &mut crate::Hasher::default(); - element.widget.hash_layout(&mut hash); - hash.finish() - }; - if hash != cached_hash { - cached_hash = hash; - element.widget.draw(root_view); + let mut cached_hash: u64 = 0; + let mut _current_element: Option> = None; + let mut widget_pointers = WidgetPointers { + root: 0 as id, + others: Vec::new(), + hash: cached_hash, + }; + + event_loop.run( + move |event: winit::event::Event, _, control_flow| { + //let new_title = application.borrow().title(); + //debug!("NEW EVENT: {:?}", event); + let mut messages: Vec = Vec::new(); + match event { + event::Event::MainEventsCleared => {} + event::Event::UserEvent(widget_event) => { + info!("GOT NEW USER EVENT: {:?}", widget_event); + + let mut element = app.view(); + element + .widget + .on_widget_event(widget_event, &mut messages, &widget_pointers); + + let hash = { + let mut hash = &mut crate::Hasher::default(); + element.widget.hash_layout(&mut hash); + hash.finish() + }; + if hash != cached_hash { + cached_hash = hash; + widget_pointers = element.widget.draw(root_view); + } + } + event::Event::RedrawRequested(_) => { + } + event::Event::WindowEvent { + event: _window_event, + .. + } => {} + event::Event::NewEvents(event::StartCause::Init) => { + let root_view: UIView = UIView(window.ui_view() as id); + let mut element = app.view(); + + let hash = { + let mut hash = &mut crate::Hasher::default(); + element.widget.hash_layout(&mut hash); + hash.finish() + }; + if hash != cached_hash { + cached_hash = hash; + widget_pointers = element.widget.draw(root_view); + } + } + _ => { + *control_flow = ControlFlow::Wait; } - */ - } - event::Event::RedrawRequested(_) => {} - event::Event::WindowEvent { - event: _window_event, - .. - } => { } event::Event::NewEvents(event::StartCause::Init) => { - /* - let root_view: UIView = UIView(window.ui_view() as id); - app.view().widget.draw(root_view); - */ - } - _ => { - *control_flow = ControlFlow::Wait; } - } - for message in messages { + for message in messages { + let (command, subscription) = runtime.enter(|| { + let command = app.update(message); + let subscription = app.subscription(); - let (command, subscription) = runtime.enter(|| { - let command = app.update(message); - let subscription = app.subscription(); + (command, subscription) + }); - (command, subscription) - }); - - - runtime.spawn(command); - runtime.track(subscription); - } - }); + runtime.spawn(command); + runtime.track(subscription); + } + }, + ); } } diff --git a/ios/src/layout.rs b/ios/src/layout.rs deleted file mode 100644 index 3dc5a9072f..0000000000 --- a/ios/src/layout.rs +++ /dev/null @@ -1,151 +0,0 @@ - -use crate::{Align, Point, Rectangle, Size, Vector}; - -/// The bounds of an element and its children. -#[derive(Debug, Clone, Default)] -pub struct Node { - bounds: Rectangle, - children: Vec, -} - -impl Node { - /// Creates a new [`Node`] with the given [`Size`]. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub const fn new(size: Size) -> Self { - Self::with_children(size, Vec::new()) - } - - /// Creates a new [`Node`] with the given [`Size`] and children. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub const fn with_children(size: Size, children: Vec) -> Self { - Node { - bounds: Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - children, - } - } - - /// Returns the [`Size`] of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub fn size(&self) -> Size { - Size::new(self.bounds.width, self.bounds.height) - } - - /// Returns the bounds of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { - self.bounds - } - - /// Returns the children of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - pub fn children(&self) -> &[Node] { - &self.children - } - - /// Aligns the [`Node`] in the given space. - /// - /// [`Node`]: struct.Node.html - pub fn align( - &mut self, - horizontal_alignment: Align, - vertical_alignment: Align, - space: Size, - ) { - match horizontal_alignment { - Align::Start => {} - Align::Center => { - self.bounds.x += (space.width - self.bounds.width) / 2.0; - } - Align::End => { - self.bounds.x += space.width - self.bounds.width; - } - } - - match vertical_alignment { - Align::Start => {} - Align::Center => { - self.bounds.y += (space.height - self.bounds.height) / 2.0; - } - Align::End => { - self.bounds.y += space.height - self.bounds.height; - } - } - } - - /// Moves the [`Node`] to the given position. - /// - /// [`Node`]: struct.Node.html - pub fn move_to(&mut self, position: Point) { - self.bounds.x = position.x; - self.bounds.y = position.y; - } -} - -/// The bounds of a [`Node`] and its children, using absolute coordinates. -/// -/// [`Node`]: struct.Node.html -#[derive(Debug, Clone, Copy)] -pub struct Layout<'a> { - position: Point, - node: &'a Node, -} - -impl<'a> Layout<'a> { - pub(crate) fn new(node: &'a Node) -> Self { - Self::with_offset(Vector::new(0.0, 0.0), node) - } - - pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { - let bounds = node.bounds(); - - Self { - position: Point::new(bounds.x, bounds.y) + offset, - node, - } - } - - /// Gets the bounds of the [`Layout`]. - /// - /// The returned [`Rectangle`] describes the position and size of a - /// [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Rectangle`]: struct.Rectangle.html - /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { - let bounds = self.node.bounds(); - - Rectangle { - x: self.position.x, - y: self.position.y, - width: bounds.width, - height: bounds.height, - } - } - - /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Node`]: struct.Node.html - pub fn children(&'a self) -> impl Iterator> { - self.node.children().iter().map(move |node| { - Layout::with_offset( - Vector::new(self.position.x, self.position.y), - node, - ) - }) - } -} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 8fc0d25830..888b9700b5 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -10,11 +10,11 @@ pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ - Element, Widget, Text, TextInput + Element, Widget, Text, TextInput, WidgetPointers, }; use event::WidgetEvent; -mod layout; -pub use layout::Layout; +//mod layout; +//pub use layout::Layout; pub use application::Application; /* //! Run commands and subscriptions. diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 53799317c7..ffd3dfeb1c 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -3,6 +3,10 @@ use crate::{ event::WidgetEvent, Hasher, }; +use uikit_sys::{ + UIView, + id, +}; pub mod container; pub mod checkbox; pub mod text; @@ -12,13 +16,32 @@ pub use container::Container; pub use checkbox::Checkbox; pub use text::Text; pub use text_input::TextInput; -use uikit_sys::UIView; + +pub struct WidgetPointers { + pub root: id, + pub others: Vec, + pub hash: u64, +} +impl Drop for WidgetPointers { + fn drop(&mut self) { + use uikit_sys::UIView_UIViewHierarchy; + unsafe { + let root = UIView(self.root); + root.removeFromSuperview(); + } + } +} pub trait Widget { fn draw( &mut self, - _parent: UIView, - ) { + parent: UIView, + ) -> WidgetPointers { + WidgetPointers { + root: parent.0, + others: Vec::new(), + hash: 0, + } } fn hash_layout(&self, state: &mut Hasher); fn on_widget_event( @@ -27,6 +50,7 @@ pub trait Widget { //_layout: Layout<'_>, //_cursor_position: Point, _messages: &mut Vec, + _widget_pointers: &WidgetPointers, //_renderer: &Renderer, //_clipboard: Option<&dyn Clipboard>, ) { @@ -48,3 +72,15 @@ impl<'a, Message> Element<'a, Message> { } } } +impl<'a, Message> Widget for Element<'a, Message> +where + Message: 'static, +{ + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + struct Marker; + std::any::TypeId::of::().hash(state); + self.widget.hash_layout(state); + } + +} diff --git a/ios/src/widget/container.rs b/ios/src/widget/container.rs index c0b5916589..b45fef3677 100644 --- a/ios/src/widget/container.rs +++ b/ios/src/widget/container.rs @@ -1,4 +1,6 @@ -use crate::{Element, Widget, Length, Align}; +use std::hash::Hash; +use crate::{Element, Widget, Length, Align, Hasher}; +pub use iced_style::text_input::{Style, StyleSheet}; #[allow(missing_debug_implementations)] pub struct Container<'a, Message> { @@ -9,7 +11,7 @@ pub struct Container<'a, Message> { max_height: u32, horizontal_alignment: Align, vertical_alignment: Align, - //style_sheet: Box, + style_sheet: Box, content: Element<'a, Message>, } @@ -31,16 +33,28 @@ impl<'a, Message> Container<'a, Message> { max_height: u32::MAX, horizontal_alignment: Align::Start, vertical_alignment: Align::Start, - //style_sheet: Default::default(), + style_sheet: Default::default(), content: content.into(), } } } -/* impl<'a, Message> Widget for Container<'a, Message> where Message: 'static, { + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.padding.hash(state); + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + + self.content.hash_layout(state); + } } impl<'a, Message> From> for Element<'a, Message> where @@ -50,4 +64,3 @@ where Element::new(container) } } -*/ diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 79054799b6..aca0d2e2a6 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -8,6 +8,7 @@ use crate::{ Length, VerticalAlignment, Widget, + WidgetPointers, Hasher, }; @@ -122,6 +123,7 @@ use uikit_sys::{ }; impl<'a, Message> Widget for Text { + fn hash_layout(&self, state: &mut Hasher) { struct Marker; std::any::TypeId::of::().hash(state); @@ -132,8 +134,9 @@ impl<'a, Message> Widget for Text { self.height.hash(state); } - fn draw(&mut self, parent: UIView) { - unsafe { + fn draw(&mut self, parent: UIView) -> WidgetPointers { + + let label = unsafe { let label = UILabel::alloc(); let text = NSString( NSString::alloc().initWithBytes_length_encoding_( @@ -168,7 +171,20 @@ impl<'a, Message> Widget for Text { } label.setFrame_(rect); parent.addSubview_(label.0); + label + }; + let hash = { + let mut hash = &mut crate::Hasher::default(); + //self.hash_layout(&mut hash); + use std::hash::Hasher; + hash.finish() }; + + WidgetPointers { + root: label.0, + others: Vec::new(), + hash, + } } } diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index f748c53852..282625dc75 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -5,12 +5,23 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::{Element, Length, Widget, Layout, Hasher, event::{EventHandler, WidgetEvent}}; +use crate::{Element, Length, Widget, Hasher, WidgetPointers, event::{EventHandler, WidgetEvent}}; pub use iced_style::text_input::{Style, StyleSheet}; use std::{rc::Rc, u32}; -use std::hash::Hash; + +use std::convert::TryInto; +use uikit_sys::{ + id, CGPoint, CGRect, CGSize, + NSString, NSString_NSStringExtensionMethods, UIView, + UIView_UIViewHierarchy, + UITextView, + IUITextView, + NSNotificationCenter, + INSNotificationCenter, + UITextViewTextDidChangeNotification, +}; /// A field that can be filled with text. /// @@ -144,19 +155,6 @@ impl<'a, Message> TextInput<'a, Message> { } } -use std::convert::TryInto; -use std::ffi::CString; -use uikit_sys::{ - id, CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, - NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, - UIView_UIViewGeometry, UIView_UIViewHierarchy, - UITextView, - IUITextView, - NSNotificationCenter, - INSNotificationCenter, - UITextViewTextDidChangeNotification, -}; - impl<'a, Message> Widget for TextInput<'a, Message> where Message: 'static + Clone, @@ -171,10 +169,7 @@ where self.padding.hash(state); self.size.hash(state); } - fn draw(&mut self, parent: UIView) { - if self.ui_textview.is_some() { - return; - } + fn draw(&mut self, parent: UIView) -> WidgetPointers { let input_rect = CGRect { origin: CGPoint { x: 10.0, @@ -216,6 +211,18 @@ where self.widget_id = on_change.widget_id; debug!("drow TEXT UIVIEW {:?}", self.ui_textview.is_some()); + use std::hash::Hasher; + let hash = { + let mut hash = &mut crate::Hasher::default(); + self.hash_layout(&mut hash); + hash.finish() + }; + + WidgetPointers { + root: self.ui_textview.unwrap().0, + others: Vec::new(), + hash, + } /* use uikit_sys::{ @@ -253,13 +260,26 @@ where */ } - fn on_widget_event(&mut self, widget_event: WidgetEvent, messages: &mut Vec) { + fn on_widget_event(&mut self, widget_event: WidgetEvent, messages: &mut Vec, widget_pointers: &WidgetPointers) { + let ui_textview = UITextView(widget_pointers.root); + let value = unsafe { + let value = NSString(ui_textview.text()); + let len = value.lengthOfBytesUsingEncoding_( + uikit_sys::NSUTF8StringEncoding, + ); + let bytes = value.UTF8String() as *const u8; + String::from_utf8(std::slice::from_raw_parts(bytes, len.try_into().unwrap()).to_vec()).unwrap() + }; + self.value = value; + + messages.push((self.on_change)(self.value.clone())); + /* debug!("on_widget_event TEXT UIVIEW {:?}", self.ui_textview.is_some()); if let Some(ui_textview) = self.ui_textview { if widget_event.widget_id == self.widget_id { let value = unsafe { - let value = NSString(self.ui_textview.unwrap().text()); + let value = NSString(ui_textview.text()); let len = value.lengthOfBytesUsingEncoding_( uikit_sys::NSUTF8StringEncoding, ); @@ -271,6 +291,7 @@ where messages.push((self.on_change)(self.value.clone())); } } + */ } } diff --git a/src/window/settings.rs b/src/window/settings.rs index eb997899ed..a826376d94 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -29,7 +29,7 @@ impl Default for Settings { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From for iced_winit::settings::Window { fn from(settings: Settings) -> Self { Self { From 1d42991aa03823d3292ff038ffcdd291b1abe2d9 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Wed, 8 Jul 2020 13:41:16 -0700 Subject: [PATCH 16/33] Addd layout stuff, got UIStackview working --- examples/ios-example/src/main.rs | 44 +++-- ios/Cargo.toml | 1 + ios/src/application.rs | 9 +- ios/src/event.rs | 8 +- ios/src/layout.rs | 68 ++++++++ ios/src/layout/DRUID_LICENSE | 202 ++++++++++++++++++++++ ios/src/layout/debugger.rs | 26 +++ ios/src/layout/flex.rs | 175 +++++++++++++++++++ ios/src/layout/limits.rs | 200 ++++++++++++++++++++++ ios/src/layout/node.rs | 94 +++++++++++ ios/src/lib.rs | 20 +-- ios/src/widget.rs | 83 ++++++++- ios/src/widget/button.rs | 178 ++++++++++++++++++++ ios/src/widget/checkbox.rs | 49 +++++- ios/src/widget/column.rs | 280 +++++++++++++++++++++++++++++++ ios/src/widget/container.rs | 33 +++- ios/src/widget/image.rs | 158 +++++++++++++++++ ios/src/widget/progress_bar.rs | 103 ++++++++++++ ios/src/widget/radio.rs | 112 +++++++++++++ ios/src/widget/row.rs | 174 +++++++++++++++++++ ios/src/widget/scrollable.rs | 166 ++++++++++++++++++ ios/src/widget/slider.rs | 173 +++++++++++++++++++ ios/src/widget/space.rs | 70 ++++++++ ios/src/widget/text.rs | 58 +++++-- ios/src/widget/text_input.rs | 164 ++++++++++-------- 25 files changed, 2501 insertions(+), 147 deletions(-) create mode 100644 ios/src/layout.rs create mode 100644 ios/src/layout/DRUID_LICENSE create mode 100644 ios/src/layout/debugger.rs create mode 100644 ios/src/layout/flex.rs create mode 100644 ios/src/layout/limits.rs create mode 100644 ios/src/layout/node.rs create mode 100644 ios/src/widget/button.rs create mode 100644 ios/src/widget/column.rs create mode 100644 ios/src/widget/image.rs create mode 100644 ios/src/widget/progress_bar.rs create mode 100644 ios/src/widget/radio.rs create mode 100644 ios/src/widget/row.rs create mode 100644 ios/src/widget/scrollable.rs create mode 100644 ios/src/widget/slider.rs create mode 100644 ios/src/widget/space.rs diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index be055c7137..107db789b4 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate log; use iced::{ - //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, + //button, scrollable, slider, text_input, Button, Checkbox, Color, + Column, //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, //Sandbox, //Scrollable, Settings, Slider, Space, Text, TextInput, Sandbox, @@ -53,9 +54,6 @@ impl Sandbox for Simple { debug!("GOT NEW MESSAGE: {:?}", message); match message { Message::TextUpdated(val) => { - if val.starts_with("a") { - self.toggle = true; - } self.text = val; }, _ => { @@ -65,27 +63,23 @@ impl Sandbox for Simple { fn view(&mut self) -> Element { debug!("RERUNNING VIEW : {:#?}", self); - if self.toggle { - /* - let toggle = Checkbox::new( - self.toggle, - "Listen to runtime events", - Message::Toggled, - ); - toggle.into() - */ + let column = Column::new() + .push( + TextInput::new( + &mut self.text_state, + "", + "", + |s| { Message::TextUpdated(s) } + ) + ) + .push( + Text::new(&self.text).color(Color::BLACK) + ) + .push( + Text::new(String::from("foo foo foo")).color(Color::BLACK) + ) + ; + column.into() - let text = Text::new(&self.text).color(Color::BLACK); - text.into() - } else { - - let text_field = TextInput::new( - &mut self.text_state, - "", - "", - |s| { Message::TextUpdated(s) } - ); - text_field.into() - } } } diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 89a46ea70d..67335885c5 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -13,6 +13,7 @@ winit = "^0.22" objc = "0.2.7" uikit-sys = { git = "https://github.com/simlay/uikit-sys" } log = "0.4" +num-traits = "0.2" #uikit-sys = { path = "../../uikit-sys/" } [dependencies.iced_core] diff --git a/ios/src/application.rs b/ios/src/application.rs index e99e86eb88..0f3bfb6deb 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,6 +1,7 @@ use crate::{ event::{EventHandler, WidgetEvent}, WidgetPointers, + widget::Widget, Command, Element, Executor, Runtime, Subscription, }; use std::hash::Hasher; @@ -11,8 +12,6 @@ use winit::{ window::WindowBuilder, }; -use std::cell::RefCell; -use std::rc::Rc; use uikit_sys::{ self, //CGRect, @@ -165,8 +164,6 @@ pub trait Application: Sized { match event { event::Event::MainEventsCleared => {} event::Event::UserEvent(widget_event) => { - info!("GOT NEW USER EVENT: {:?}", widget_event); - let mut element = app.view(); element .widget @@ -179,7 +176,7 @@ pub trait Application: Sized { }; if hash != cached_hash { cached_hash = hash; - widget_pointers = element.widget.draw(root_view); + widget_pointers = element.draw(root_view); } } event::Event::RedrawRequested(_) => { @@ -199,7 +196,7 @@ pub trait Application: Sized { }; if hash != cached_hash { cached_hash = hash; - widget_pointers = element.widget.draw(root_view); + widget_pointers = element.draw(root_view); } } _ => { diff --git a/ios/src/event.rs b/ios/src/event.rs index 0ecbb06863..8432cf3ff0 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -15,6 +15,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[derive(PartialEq, Clone, Debug)] pub struct WidgetEvent { pub widget_id: u64, + pub id: usize, } #[derive(Debug)] @@ -32,7 +33,7 @@ impl EventHandler { PROXY = Some(proxy); } } - pub fn new() -> Self + pub fn new(objc_id: id) -> Self { let mut widget_id = 0; let obj = unsafe { @@ -42,6 +43,7 @@ impl EventHandler { if let Some(counter) = &COUNTER { widget_id = counter.fetch_add(0, Ordering::Relaxed); (*obj).set_ivar::("widget_id", widget_id); + (*obj).set_ivar::("objc_id", objc_id); } obj }; @@ -52,7 +54,8 @@ impl EventHandler { unsafe { if let Some(ref proxy) = PROXY { let widget_id = *this.get_ivar::("widget_id"); - let _ = proxy.send_event(WidgetEvent { widget_id } ); + let id = *this.get_ivar::("objc_id"); + let _ = proxy.send_event(WidgetEvent { widget_id, id: id as usize} ); } } } @@ -72,6 +75,7 @@ impl EventHandler { ); } decl.add_ivar::("widget_id"); + decl.add_ivar::("objc_id"); decl.register() } } diff --git a/ios/src/layout.rs b/ios/src/layout.rs new file mode 100644 index 0000000000..c9eea939df --- /dev/null +++ b/ios/src/layout.rs @@ -0,0 +1,68 @@ +//! Position your widgets properly. +//mod debugger; +mod limits; +mod node; + +pub mod flex; + +//pub use debugger::Debugger; +pub use limits::Limits; +pub use node::Node; + +use crate::{Point, Rectangle, Vector}; + +/// The bounds of a [`Node`] and its children, using absolute coordinates. +/// +/// [`Node`]: struct.Node.html +#[derive(Debug, Clone, Copy)] +pub struct Layout<'a> { + position: Point, + node: &'a Node, +} + +impl<'a> Layout<'a> { + pub(crate) fn new(node: &'a Node) -> Self { + Self::with_offset(Vector::new(0.0, 0.0), node) + } + + pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { + let bounds = node.bounds(); + + Self { + position: Point::new(bounds.x, bounds.y) + offset, + node, + } + } + + /// Gets the bounds of the [`Layout`]. + /// + /// The returned [`Rectangle`] describes the position and size of a + /// [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Rectangle`]: struct.Rectangle.html + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + let bounds = self.node.bounds(); + + Rectangle { + x: self.position.x, + y: self.position.y, + width: bounds.width, + height: bounds.height, + } + } + + /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Node`]: struct.Node.html + pub fn children(&'a self) -> impl Iterator> { + self.node.children().iter().map(move |node| { + Layout::with_offset( + Vector::new(self.position.x, self.position.y), + node, + ) + }) + } +} diff --git a/ios/src/layout/DRUID_LICENSE b/ios/src/layout/DRUID_LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/ios/src/layout/DRUID_LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ios/src/layout/debugger.rs b/ios/src/layout/debugger.rs new file mode 100644 index 0000000000..e4b21609cc --- /dev/null +++ b/ios/src/layout/debugger.rs @@ -0,0 +1,26 @@ +use crate::{Color, Layout, Point, Renderer, Widget}; + +/// A renderer able to graphically explain a [`Layout`]. +/// +/// [`Layout`]: struct.Layout.html +pub trait Debugger: Renderer { + /// Explains the [`Layout`] of an [`Element`] for debugging purposes. + /// + /// This will be called when [`Element::explain`] has been used. It should + /// _explain_ the given [`Layout`] graphically. + /// + /// A common approach consists in recursively rendering the bounds of the + /// [`Layout`] and its children. + /// + /// [`Layout`]: struct.Layout.html + /// [`Element`]: ../struct.Element.html + /// [`Element::explain`]: ../struct.Element.html#method.explain + fn explain( + &mut self, + defaults: &Self::Defaults, + widget: &dyn Widget, + layout: Layout<'_>, + cursor_position: Point, + color: Color, + ) -> Self::Output; +} diff --git a/ios/src/layout/flex.rs b/ios/src/layout/flex.rs new file mode 100644 index 0000000000..77ad20f49e --- /dev/null +++ b/ios/src/layout/flex.rs @@ -0,0 +1,175 @@ +//! Distribute elements using a flex-based layout. +// This code is heavily inspired by the [`druid`] codebase. +// +// [`druid`]: https://github.com/xi-editor/druid +// +// Copyright 2018 The xi-editor Authors, Héctor Ramón +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{ + layout::{Limits, Node}, + Align, Element, Point, Size, + widget::Widget, +}; + +/// The main axis of a flex layout. +#[derive(Debug)] +pub enum Axis { + /// The horizontal axis + Horizontal, + + /// The vertical axis + Vertical, +} + +impl Axis { + fn main(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.width, + Axis::Vertical => size.height, + } + } + + fn cross(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.height, + Axis::Vertical => size.width, + } + } + + fn pack(&self, main: f32, cross: f32) -> (f32, f32) { + match self { + Axis::Horizontal => (main, cross), + Axis::Vertical => (cross, main), + } + } +} + +/// Computes the flex layout with the given axis and limits, applying spacing, +/// padding and alignment to the items as needed. +/// +/// It returns a new layout [`Node`]. +/// +/// [`Node`]: ../struct.Node.html +pub fn resolve( + axis: Axis, + limits: &Limits, + padding: f32, + spacing: f32, + align_items: Align, + items: &[Element<'_, Message>], +) -> Node +where + Message: 'static, +{ + let limits = limits.pad(padding); + let total_spacing = spacing * items.len().saturating_sub(1) as f32; + let max_cross = axis.cross(limits.max()); + + let mut fill_sum = 0; + let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill())); + let mut available = axis.main(limits.max()) - total_spacing; + + let mut nodes: Vec = Vec::with_capacity(items.len()); + nodes.resize(items.len(), Node::default()); + + for (i, child) in items.iter().enumerate() { + let fill_factor = match axis { + Axis::Horizontal => child.width(), + Axis::Vertical => child.height(), + } + .fill_factor(); + + if fill_factor == 0 { + let (max_width, max_height) = axis.pack(available, max_cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = child.layout(&child_limits); + let size = layout.size(); + + available -= axis.main(size); + cross = cross.max(axis.cross(size)); + + nodes[i] = layout; + } else { + fill_sum += fill_factor; + } + } + + let remaining = available.max(0.0); + + for (i, child) in items.iter().enumerate() { + let fill_factor = match axis { + Axis::Horizontal => child.width(), + Axis::Vertical => child.height(), + } + .fill_factor(); + + if fill_factor != 0 { + let max_main = remaining * fill_factor as f32 / fill_sum as f32; + let min_main = if max_main.is_infinite() { + 0.0 + } else { + max_main + }; + + let (min_main, min_cross) = + axis.pack(min_main, axis.cross(limits.min())); + + let (max_main, max_cross) = + axis.pack(max_main, axis.cross(limits.max())); + + let child_limits = Limits::new( + Size::new(min_main, min_cross), + Size::new(max_main, max_cross), + ); + + let layout = child.layout(&child_limits); + cross = cross.max(axis.cross(layout.size())); + + nodes[i] = layout; + } + } + + let mut main = padding; + + for (i, node) in nodes.iter_mut().enumerate() { + if i > 0 { + main += spacing; + } + + let (x, y) = axis.pack(main, padding); + + node.move_to(Point::new(x, y)); + + match axis { + Axis::Horizontal => { + node.align(Align::Start, align_items, Size::new(0.0, cross)); + } + Axis::Vertical => { + node.align(align_items, Align::Start, Size::new(cross, 0.0)); + } + } + + let size = node.size(); + + main += axis.main(size); + } + + let (width, height) = axis.pack(main - padding, cross); + let size = limits.resolve(Size::new(width, height)); + + Node::with_children(size.pad(padding), nodes) +} diff --git a/ios/src/layout/limits.rs b/ios/src/layout/limits.rs new file mode 100644 index 0000000000..664c881a4b --- /dev/null +++ b/ios/src/layout/limits.rs @@ -0,0 +1,200 @@ +use crate::{Length, Size}; + +/// A set of size constraints for layouting. +#[derive(Debug, Clone, Copy)] +pub struct Limits { + min: Size, + max: Size, + fill: Size, +} + +impl Limits { + /// No limits + pub const NONE: Limits = Limits { + min: Size::ZERO, + max: Size::INFINITY, + fill: Size::INFINITY, + }; + + /// Creates new [`Limits`] with the given minimum and maximum [`Size`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html + pub const fn new(min: Size, max: Size) -> Limits { + Limits { + min, + max, + fill: Size::INFINITY, + } + } + + /// Returns the minimum [`Size`] of the [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html + pub fn min(&self) -> Size { + self.min + } + + /// Returns the maximum [`Size`] of the [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html + pub fn max(&self) -> Size { + self.max + } + + /// Returns the fill [`Size`] of the [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html + pub fn fill(&self) -> Size { + self.fill + } + + /// Applies a width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn width(mut self, width: Length) -> Limits { + match width { + Length::Shrink => { + self.fill.width = self.min.width; + } + Length::Fill | Length::FillPortion(_) => { + self.fill.width = self.fill.width.min(self.max.width); + } + Length::Units(units) => { + let new_width = + (units as f32).min(self.max.width).max(self.min.width); + + self.min.width = new_width; + self.max.width = new_width; + self.fill.width = new_width; + } + } + + self + } + + /// Applies a height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn height(mut self, height: Length) -> Limits { + match height { + Length::Shrink => { + self.fill.height = self.min.height; + } + Length::Fill | Length::FillPortion(_) => { + self.fill.height = self.fill.height.min(self.max.height); + } + Length::Units(units) => { + let new_height = + (units as f32).min(self.max.height).max(self.min.height); + + self.min.height = new_height; + self.max.height = new_height; + self.fill.height = new_height; + } + } + + self + } + + /// Applies a minimum width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn min_width(mut self, min_width: u32) -> Limits { + self.min.width = + self.min.width.max(min_width as f32).min(self.max.width); + + self + } + + /// Applies a maximum width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn max_width(mut self, max_width: u32) -> Limits { + self.max.width = + self.max.width.min(max_width as f32).max(self.min.width); + + self + } + + /// Applies a minimum height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn min_height(mut self, min_height: u32) -> Limits { + self.min.height = + self.min.height.max(min_height as f32).min(self.max.height); + + self + } + + /// Applies a maximum height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn max_height(mut self, max_height: u32) -> Limits { + self.max.height = + self.max.height.min(max_height as f32).max(self.min.height); + + self + } + + /// Shrinks the current [`Limits`] to account for the given padding. + /// + /// [`Limits`]: struct.Limits.html + pub fn pad(&self, padding: f32) -> Limits { + self.shrink(Size::new(padding * 2.0, padding * 2.0)) + } + + /// Shrinks the current [`Limits`] by the given [`Size`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html + pub fn shrink(&self, size: Size) -> Limits { + let min = Size::new( + (self.min().width - size.width).max(0.0), + (self.min().height - size.height).max(0.0), + ); + + let max = Size::new( + (self.max().width - size.width).max(0.0), + (self.max().height - size.height).max(0.0), + ); + + let fill = Size::new( + (self.fill.width - size.width).max(0.0), + (self.fill.height - size.height).max(0.0), + ); + + Limits { min, max, fill } + } + + /// Removes the minimum width constraint for the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn loose(&self) -> Limits { + Limits { + min: Size::ZERO, + max: self.max, + fill: self.fill, + } + } + + /// Computes the resulting [`Size`] that fits the [`Limits`] given the + /// intrinsic size of some content. + /// + /// [`Limits`]: struct.Limits.html + pub fn resolve(&self, intrinsic_size: Size) -> Size { + Size::new( + intrinsic_size + .width + .min(self.max.width) + .max(self.fill.width), + intrinsic_size + .height + .min(self.max.height) + .max(self.fill.height), + ) + } +} diff --git a/ios/src/layout/node.rs b/ios/src/layout/node.rs new file mode 100644 index 0000000000..a265c46a06 --- /dev/null +++ b/ios/src/layout/node.rs @@ -0,0 +1,94 @@ +use crate::{Align, Point, Rectangle, Size}; + +/// The bounds of an element and its children. +#[derive(Debug, Clone, Default)] +pub struct Node { + bounds: Rectangle, + children: Vec, +} + +impl Node { + /// Creates a new [`Node`] with the given [`Size`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn new(size: Size) -> Self { + Self::with_children(size, Vec::new()) + } + + /// Creates a new [`Node`] with the given [`Size`] and children. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub const fn with_children(size: Size, children: Vec) -> Self { + Node { + bounds: Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + children, + } + } + + /// Returns the [`Size`] of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html + pub fn size(&self) -> Size { + Size::new(self.bounds.width, self.bounds.height) + } + + /// Returns the bounds of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn bounds(&self) -> Rectangle { + self.bounds + } + + /// Returns the children of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + pub fn children(&self) -> &[Node] { + &self.children + } + + /// Aligns the [`Node`] in the given space. + /// + /// [`Node`]: struct.Node.html + pub fn align( + &mut self, + horizontal_alignment: Align, + vertical_alignment: Align, + space: Size, + ) { + match horizontal_alignment { + Align::Start => {} + Align::Center => { + self.bounds.x += (space.width - self.bounds.width) / 2.0; + } + Align::End => { + self.bounds.x += space.width - self.bounds.width; + } + } + + match vertical_alignment { + Align::Start => {} + Align::Center => { + self.bounds.y += (space.height - self.bounds.height) / 2.0; + } + Align::End => { + self.bounds.y += space.height - self.bounds.height; + } + } + } + + /// Moves the [`Node`] to the given position. + /// + /// [`Node`]: struct.Node.html + pub fn move_to(&mut self, position: Point) { + self.bounds.x = position.x; + self.bounds.y = position.y; + } +} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 888b9700b5..1f8d79e8d8 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -10,26 +10,12 @@ pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ - Element, Widget, Text, TextInput, WidgetPointers, + Element, Widget, Text, TextInput, WidgetPointers, Column, }; use event::WidgetEvent; -//mod layout; -//pub use layout::Layout; +mod layout; +pub use layout::Layout; pub use application::Application; -/* -//! Run commands and subscriptions. -use crate::{Event, Hasher}; - -/// A native runtime with a generic executor and receiver of results. -/// -/// It can be used by shells to easily spawn a [`Command`] or track a -/// [`Subscription`]. -/// -/// [`Command`]: ../struct.Command.html -/// [`Subscription`]: ../struct.Subscription.html -pub type Runtime = - iced_futures::Runtime; -*/ pub type Hasher = std::collections::hash_map::DefaultHasher; pub type Runtime = diff --git a/ios/src/widget.rs b/ios/src/widget.rs index ffd3dfeb1c..ecf871228e 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,5 +1,8 @@ use crate::{ - //Layout, + layout::{self, + Layout, + }, + Length, event::WidgetEvent, Hasher, }; @@ -7,21 +10,49 @@ use uikit_sys::{ UIView, id, }; -pub mod container; + +pub mod button; pub mod checkbox; -pub mod text; +pub mod container; +pub mod image; +pub mod progress_bar; +pub mod radio; +pub mod scrollable; +pub mod slider; pub mod text_input; -pub use container::Container; -pub use checkbox::Checkbox; +mod column; +mod row; +mod space; +mod text; + +#[doc(no_inline)] +pub use button::Button; +#[doc(no_inline)] +pub use scrollable::Scrollable; +#[doc(no_inline)] +pub use slider::Slider; +#[doc(no_inline)] pub use text::Text; +#[doc(no_inline)] pub use text_input::TextInput; +pub use checkbox::Checkbox; +pub use column::Column; +pub use container::Container; +pub use image::Image; +pub use progress_bar::ProgressBar; +pub use radio::Radio; +pub use row::Row; +pub use space::Space; + pub struct WidgetPointers { pub root: id, pub others: Vec, pub hash: u64, + //child: Option>, } +/* impl Drop for WidgetPointers { fn drop(&mut self) { use uikit_sys::UIView_UIViewHierarchy; @@ -31,6 +62,7 @@ impl Drop for WidgetPointers { } } } +*/ pub trait Widget { fn draw( @@ -41,6 +73,7 @@ pub trait Widget { root: parent.0, others: Vec::new(), hash: 0, + //child: None } } fn hash_layout(&self, state: &mut Hasher); @@ -55,6 +88,14 @@ pub trait Widget { //_clipboard: Option<&dyn Clipboard>, ) { } + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node; + + fn width(&self) -> Length; + + fn height(&self) -> Length; } #[allow(missing_debug_implementations)] @@ -73,8 +114,6 @@ impl<'a, Message> Element<'a, Message> { } } impl<'a, Message> Widget for Element<'a, Message> -where - Message: 'static, { fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash; @@ -83,4 +122,34 @@ where self.widget.hash_layout(state); } + fn layout( + &self, + _limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } + + fn draw(&mut self, parent: UIView) -> WidgetPointers { + self.widget.draw(parent) + } + fn on_widget_event( + &mut self, + event: WidgetEvent, + //_layout: Layout<'_>, + //_cursor_position: Point, + messages: &mut Vec, + widget_pointers: &WidgetPointers, + //_renderer: &Renderer, + //_clipboard: Option<&dyn Clipboard>, + ) { + self.widget.on_widget_event(event, messages, widget_pointers); + } } diff --git a/ios/src/widget/button.rs b/ios/src/widget/button.rs new file mode 100644 index 0000000000..b42156c255 --- /dev/null +++ b/ios/src/widget/button.rs @@ -0,0 +1,178 @@ +//! Allow your users to perform actions by pressing a button. +//! +//! A [`Button`] has some local [`State`]. +//! +//! [`Button`]: struct.Button.html +//! [`State`]: struct.State.html +use crate::{Background, Element, Length, Widget, Hasher, layout, Point}; +use std::hash::Hash; + +pub use iced_style::button::{Style, StyleSheet}; + + +/// A generic widget that produces a message when pressed. +/// +/// ``` +/// # use iced_web::{button, Button, Text}; +/// # +/// enum Message { +/// ButtonPressed, +/// } +/// +/// let mut state = button::State::new(); +/// let button = Button::new(&mut state, Text::new("Press me!")) +/// .on_press(Message::ButtonPressed); +/// ``` +#[allow(missing_debug_implementations)] +pub struct Button<'a, Message> { + content: Element<'a, Message>, + on_press: Option, + width: Length, + height: Length, + min_width: u32, + min_height: u32, + padding: u16, + style: Box, +} + +impl<'a, Message> Button<'a, Message> { + /// Creates a new [`Button`] with some local [`State`] and the given + /// content. + /// + /// [`Button`]: struct.Button.html + /// [`State`]: struct.State.html + pub fn new(_state: &'a mut State, content: E) -> Self + where + E: Into>, + { + Button { + content: content.into(), + on_press: None, + width: Length::Shrink, + height: Length::Shrink, + min_width: 0, + min_height: 0, + padding: 5, + style: Default::default(), + } + } + + /// Sets the width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the minimum width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn min_width(mut self, min_width: u32) -> Self { + self.min_width = min_width; + self + } + + /// Sets the minimum height of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn min_height(mut self, min_height: u32) -> Self { + self.min_height = min_height; + self + } + + /// Sets the padding of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + /// Sets the style of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed. + /// + /// [`Button`]: struct.Button.html + pub fn on_press(mut self, msg: Message) -> Self { + self.on_press = Some(msg); + self + } +} + +/// The local state of a [`Button`]. +/// +/// [`Button`]: struct.Button.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State; + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> State { + State::default() + } +} + +impl<'a, Message> Widget for Button<'a, Message> +where + Message: 'static + Clone, +{ + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.content.hash_layout(state); + } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + let padding = f32::from(self.padding); + let limits = limits + .min_width(self.min_width) + .min_height(self.min_height) + .width(self.width) + .height(self.height) + .pad(padding); + + let mut content = self.content.layout(&limits); + content.move_to(Point::new(padding, padding)); + + let size = limits.resolve(content.size()).pad(padding); + + layout::Node::with_children(size, vec![content]) + } +} +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static + Clone, +{ + fn from(button: Button<'a, Message>) -> Element<'a, Message> { + Element::new(button) + } +} diff --git a/ios/src/widget/checkbox.rs b/ios/src/widget/checkbox.rs index e4d399c5dc..a49b226462 100644 --- a/ios/src/widget/checkbox.rs +++ b/ios/src/widget/checkbox.rs @@ -1,5 +1,7 @@ -use crate::{Element, Widget, Length, Align}; +use crate::{Element, Widget, Length, Align, Hasher, layout, widget::{Text, Row}}; +use std::hash::Hash; use std::rc::Rc; +use iced_style::checkbox::StyleSheet; #[allow(missing_debug_implementations)] pub struct Checkbox { @@ -7,7 +9,7 @@ pub struct Checkbox { on_toggle: Rc Message>, label: String, width: Length, - //style: Box, + style: Box, } impl Checkbox { @@ -20,15 +22,53 @@ impl Checkbox { on_toggle: Rc::new(f), label: label.into(), width: Length::Shrink, - //style: Default::default(), + style: Default::default(), } } } -/* impl Widget for Checkbox where Message: 'static, { + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.label.hash(state); + } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + /* + Row::<()>::new() + .width(self.width) + .spacing(self.spacing) + .align_items(Align::Center) + .push( + Row::new() + .width(Length::Units(self.size)) + .height(Length::Units(self.size)), + ) + * TODO: Add the text + .push( + Text::new(&self.label) + .width(self.width) + .size(self.text_size.unwrap_or(renderer.default_size())), + ) + .layout(limits) + */ + todo!(); + } + } impl<'a, Message> From> for Element<'a, Message> where @@ -38,4 +78,3 @@ where Element::new(checkbox) } } -*/ diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs new file mode 100644 index 0000000000..99a7a1bd6a --- /dev/null +++ b/ios/src/widget/column.rs @@ -0,0 +1,280 @@ +use crate::{Align, event::WidgetEvent, Element, Length, Widget, Hasher, layout, WidgetPointers}; +use std::hash::Hash; + + +use std::u32; + +/// A container that distributes its contents vertically. +/// +/// A [`Column`] will try to fill the horizontal space of its container. +/// +/// [`Column`]: struct.Column.html +#[allow(missing_debug_implementations)] +pub struct Column<'a, Message> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec>, +} + +impl<'a, Message> Column<'a, Message> { + /// Creates an empty [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn new() -> Self { + Self::with_children(Vec::new()) + } + + /// Creates a [`Column`] with the given elements. + /// + /// [`Column`]: struct.Column.html + pub fn with_children(children: Vec>) -> Self { + Column { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Column`] in pixels. + /// + /// [`Column`]: struct.Column.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + /// + /// [`Column`]: struct.Column.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an element to the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn push(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} +use uikit_sys::{ + UIView, + id, + UIStackView, + IUIStackView, + UIView_UIViewRendering, + CGRect, CGPoint, CGSize, + INSObject, + UIColor, IUIColor, + UIView_UIViewHierarchy, + UIView_UIViewGeometry, + + NSLayoutDimension, + INSLayoutDimension, + NSLayoutConstraint, + INSLayoutConstraint, + UIView_UIViewLayoutConstraintCreation, + UIStackViewDistribution_UIStackViewDistributionFill, + UITextView, + IUITextView, + + + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + UIStackViewAlignment_UIStackViewAlignmentCenter, +}; + +impl<'a, Message> Widget for Column<'a, Message> +where + Message: 'static, +{ + fn on_widget_event( + &mut self, + event: WidgetEvent, + //_layout: Layout<'_>, + //_cursor_position: Point, + messages: &mut Vec, + widget_pointers: &WidgetPointers, + //_renderer: &Renderer, + //_clipboard: Option<&dyn Clipboard>, + ) { + debug!("on_widget_event for column"); + for i in &mut self.children { + debug!("on_widget_event for child!"); + i.on_widget_event(event.clone(), messages, widget_pointers); + } + } + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.align_items.hash(state); + self.spacing.hash(state); + + for child in &self.children { + child.widget.hash_layout(state); + } + } + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height); + + layout::flex::resolve( + layout::flex::Axis::Vertical, + &limits, + self.padding as f32, + self.spacing as f32, + self.align_items, + &self.children, + ) + } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn draw(&mut self, parent: UIView) -> WidgetPointers { + let stack_view = unsafe { + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 400.0, + width: 400.0, + }, + }; + let stack_view = UIStackView(UIStackView::alloc().initWithFrame_(rect)); + //let stack_view = UIStackView(UIStackView::alloc().init()); + //stack_view.setFrame_(rect); + stack_view.setAxis_(UILayoutConstraintAxis_UILayoutConstraintAxisVertical); + stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setDistribution_(UIStackViewDistribution_UIStackViewDistributionFill); + stack_view.setSpacing_(10.0); + for i in &mut self.children { + let subview = UIView(i.draw(UIView(0 as id)).root); + debug!("SUBVIEW VALUE: {:?}, PARENT: {:?}", subview.0, parent.0); + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + + /* + let view3 = UITextView(UITextView::alloc().init()); + let layout = NSLayoutDimension(view3.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + let layout = NSLayoutDimension(view3.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(120.0)).setActive_(true); + stack_view.addArrangedSubview_(view3.0); + + let view1 = UIView(UIView::alloc().init()); + view1.setBackgroundColor_(UIColor::redColor()); + let layout = NSLayoutDimension(view1.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + + let layout = NSLayoutDimension(view1.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + stack_view.addArrangedSubview_(view1.0); + + let view2 = UIView(UIView::alloc().init()); + view2.setBackgroundColor_(UIColor::blueColor()); + let layout = NSLayoutDimension(view2.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + let layout = NSLayoutDimension(view2.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + stack_view.addArrangedSubview_(view2.0); + */ + + parent.addSubview_(stack_view.0); + + //let background = UIColor(UIColor::greenColor()); + //let background = UIColor(UIColor::blackColor()); + //stack_view.setBackgroundColor_(background.0); + stack_view + }; + WidgetPointers { + root: stack_view.0, + others: Vec::new(), + hash: 0, + //children: None + } + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(column: Column<'a, Message>) -> Element<'a, Message> { + Element::new(column) + } +} diff --git a/ios/src/widget/container.rs b/ios/src/widget/container.rs index b45fef3677..18f6593340 100644 --- a/ios/src/widget/container.rs +++ b/ios/src/widget/container.rs @@ -1,5 +1,5 @@ use std::hash::Hash; -use crate::{Element, Widget, Length, Align, Hasher}; +use crate::{Element, Widget, Length, Align, Hasher, layout, Point}; pub use iced_style::text_input::{Style, StyleSheet}; #[allow(missing_debug_implementations)] @@ -55,6 +55,37 @@ where self.content.hash_layout(state); } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + let padding = f32::from(self.padding); + + let limits = limits + .loose() + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height) + .pad(padding); + + let mut content = self.content.layout(&limits.loose()); + let size = limits.resolve(content.size()); + + content.move_to(Point::new(padding, padding)); + content.align(self.horizontal_alignment, self.vertical_alignment, size); + + layout::Node::with_children(size.pad(padding), vec![content]) + } } impl<'a, Message> From> for Element<'a, Message> where diff --git a/ios/src/widget/image.rs b/ios/src/widget/image.rs new file mode 100644 index 0000000000..cc3e2a1914 --- /dev/null +++ b/ios/src/widget/image.rs @@ -0,0 +1,158 @@ +//! Display images in your user interface. +use crate::{Element, Hasher, Length, Widget, layout}; + +use std::{ + hash::{Hash, Hasher as _}, + path::PathBuf, + sync::Arc, +}; + +/// A frame that displays an image while keeping aspect ratio. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Image; +/// +/// let image = Image::new("resources/ferris.png"); +/// ``` +#[derive(Debug)] +pub struct Image { + /// The image path + pub handle: Handle, + + /// The width of the image + pub width: Length, + + /// The height of the image + pub height: Length, +} + +impl Image { + /// Creates a new [`Image`] with the given path. + /// + /// [`Image`]: struct.Image.html + pub fn new>(handle: T) -> Self { + Image { + handle: handle.into(), + width: Length::Shrink, + height: Length::Shrink, + } + } + + /// Sets the width of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl Widget for Image { + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + self.width.hash(state); + self.height.hash(state); + } + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, Message> From for Element<'a, Message> { + fn from(image: Image) -> Element<'a, Message> { + Element::new(image) + } +} + +/// An [`Image`] handle. +/// +/// [`Image`]: struct.Image.html +#[derive(Debug, Clone)] +pub struct Handle { + id: u64, + data: Arc, +} + +impl Handle { + /// Creates an image [`Handle`] pointing to the image of the given path. + /// + /// [`Handle`]: struct.Handle.html + pub fn from_path>(path: T) -> Handle { + Self::from_data(Data::Path(path.into())) + } + + fn from_data(data: Data) -> Handle { + let mut hasher = Hasher::default(); + data.hash(&mut hasher); + + Handle { + id: hasher.finish(), + data: Arc::new(data), + } + } + + /// Returns the unique identifier of the [`Handle`]. + /// + /// [`Handle`]: struct.Handle.html + pub fn id(&self) -> u64 { + self.id + } + + /// Returns a reference to the image [`Data`]. + /// + /// [`Data`]: enum.Data.html + pub fn data(&self) -> &Data { + &self.data + } +} + +impl From for Handle { + fn from(path: String) -> Handle { + Handle::from_path(path) + } +} + +impl From<&str> for Handle { + fn from(path: &str) -> Handle { + Handle::from_path(path) + } +} + +/// The data of an [`Image`]. +/// +/// [`Image`]: struct.Image.html +#[derive(Clone, Hash)] +pub enum Data { + /// A remote image + Path(PathBuf), +} + +impl std::fmt::Debug for Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Data::Path(path) => write!(f, "Path({:?})", path), + } + } +} diff --git a/ios/src/widget/progress_bar.rs b/ios/src/widget/progress_bar.rs new file mode 100644 index 0000000000..0cf58d44a8 --- /dev/null +++ b/ios/src/widget/progress_bar.rs @@ -0,0 +1,103 @@ +//! Provide progress feedback to your users. +use crate::{Element, Length, Widget, Hasher, layout}; +use std::hash::Hash; + +pub use iced_style::progress_bar::{Style, StyleSheet}; + +use std::ops::RangeInclusive; + +/// A bar that displays progress. +/// +/// # Example +/// ``` +/// use iced_web::ProgressBar; +/// +/// let value = 50.0; +/// +/// ProgressBar::new(0.0..=100.0, value); +/// ``` +/// +/// ![Progress bar](https://user-images.githubusercontent.com/18618951/71662391-a316c200-2d51-11ea-9cef-52758cab85e3.png) +#[allow(missing_debug_implementations)] +pub struct ProgressBar { + range: RangeInclusive, + value: f32, + width: Length, + height: Option, + style: Box, +} + +impl ProgressBar { + /// Creates a new [`ProgressBar`]. + /// + /// It expects: + /// * an inclusive range of possible values + /// * the current value of the [`ProgressBar`] + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn new(range: RangeInclusive, value: f32) -> Self { + ProgressBar { + value: value.max(*range.start()).min(*range.end()), + range, + width: Length::Fill, + height: None, + style: Default::default(), + } + } + + /// Sets the width of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn height(mut self, height: Length) -> Self { + self.height = Some(height); + self + } + + /// Sets the style of the [`ProgressBar`]. + /// + /// [`ProgressBar`]: struct.ProgressBar.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); + self + } +} + +impl Widget for ProgressBar { + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + self.width.hash(state); + self.height.hash(state); + } + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, Message> From for Element<'a, Message> +where + Message: 'static, +{ + fn from(container: ProgressBar) -> Element<'a, Message> { + Element::new(container) + } +} diff --git a/ios/src/widget/radio.rs b/ios/src/widget/radio.rs new file mode 100644 index 0000000000..844a6a0485 --- /dev/null +++ b/ios/src/widget/radio.rs @@ -0,0 +1,112 @@ +//! Create choices using radio buttons. +use crate::{Element, Widget, Hasher, layout, Length}; +use std::hash::Hash; + +pub use iced_style::radio::{Style, StyleSheet}; + + +/// A circular button representing a choice. +/// +/// # Example +/// ``` +/// # use iced_web::Radio; +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// pub enum Choice { +/// A, +/// B, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// pub enum Message { +/// RadioSelected(Choice), +/// } +/// +/// let selected_choice = Some(Choice::A); +/// +/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); +/// +/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); +/// ``` +/// +/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) +#[allow(missing_debug_implementations)] +pub struct Radio { + is_selected: bool, + on_click: Message, + label: String, + style: Box, +} + +impl Radio { + /// Creates a new [`Radio`] button. + /// + /// It expects: + /// * the value related to the [`Radio`] button + /// * the label of the [`Radio`] button + /// * the current selected value + /// * a function that will be called when the [`Radio`] is selected. It + /// receives the value of the radio and must produce a `Message`. + /// + /// [`Radio`]: struct.Radio.html + pub fn new( + value: V, + label: impl Into, + selected: Option, + f: F, + ) -> Self + where + V: Eq + Copy, + F: 'static + Fn(V) -> Message, + { + Radio { + is_selected: Some(value) == selected, + on_click: f(value), + label: label.into(), + style: Default::default(), + } + } + + /// Sets the style of the [`Radio`] button. + /// + /// [`Radio`]: struct.Radio.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); + self + } +} + +impl Widget for Radio +where + Message: 'static + Clone, +{ + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.label.hash(state); + } + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static + Clone, +{ + fn from(radio: Radio) -> Element<'a, Message> { + Element::new(radio) + } +} diff --git a/ios/src/widget/row.rs b/ios/src/widget/row.rs new file mode 100644 index 0000000000..97d6fb7f00 --- /dev/null +++ b/ios/src/widget/row.rs @@ -0,0 +1,174 @@ +use crate::{Align, Element, Length, Widget, Hasher, layout}; +use std::hash::Hash; + +use std::u32; + +/// A container that distributes its contents horizontally. +/// +/// A [`Row`] will try to fill the horizontal space of its container. +/// +/// [`Row`]: struct.Row.html +#[allow(missing_debug_implementations)] +pub struct Row<'a, Message> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec>, +} + +impl<'a, Message> Row<'a, Message> { + /// Creates an empty [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn new() -> Self { + Self::with_children(Vec::new()) + } + + /// Creates a [`Row`] with the given elements. + /// + /// [`Row`]: struct.Row.html + pub fn with_children(children: Vec>) -> Self { + Row { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + + /// Sets the horizontal spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the vertical alignment of the contents of the [`Row`] . + /// + /// [`Row`]: struct.Row.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an [`Element`] to the [`Row`]. + /// + /// [`Element`]: ../struct.Element.html + /// [`Row`]: struct.Row.html + pub fn push(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} + +impl<'a, Message> Widget for Row<'a, Message> +where + Message: 'static, +{ + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.align_items.hash(state); + self.spacing.hash(state); + self.spacing.hash(state); + + for child in &self.children { + child.widget.hash_layout(state); + } + } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height); + + layout::flex::resolve( + layout::flex::Axis::Horizontal, + &limits, + self.padding as f32, + self.spacing as f32, + self.align_items, + &self.children, + ) + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(column: Row<'a, Message>) -> Element<'a, Message> { + Element::new(column) + } +} diff --git a/ios/src/widget/scrollable.rs b/ios/src/widget/scrollable.rs new file mode 100644 index 0000000000..4a2891ce6f --- /dev/null +++ b/ios/src/widget/scrollable.rs @@ -0,0 +1,166 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::{Align, Column, Element, Length, Widget, Hasher, layout}; +use std::hash::Hash; + + +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; + +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +#[allow(missing_debug_implementations)] +pub struct Scrollable<'a, Message> { + width: Length, + height: Length, + max_height: u32, + content: Column<'a, Message>, + style: Box, +} + +impl<'a, Message> Scrollable<'a, Message> { + /// Creates a new [`Scrollable`] with the given [`State`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn new(_state: &'a mut State) -> Self { + use std::u32; + + Scrollable { + width: Length::Fill, + height: Length::Shrink, + max_height: u32::MAX, + content: Column::new(), + style: Default::default(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.content = self.content.spacing(units); + self + } + + /// Sets the padding of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn padding(mut self, units: u16) -> Self { + self.content = self.content.padding(units); + self + } + + /// Sets the width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.content = self.content.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Scrollable`] in pixels. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn align_items(mut self, align_items: Align) -> Self { + self.content = self.content.align_items(align_items); + self + } + + /// Sets the style of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); + self + } + + /// Adds an element to the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn push(mut self, child: E) -> Self + where + E: Into>, + { + self.content = self.content.push(child); + self + } +} + +impl<'a, Message> Widget for Scrollable<'a, Message> +where + Message: 'static, +{ + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.height.hash(state); + self.max_height.hash(state); + + self.content.hash_layout(state) + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, Message> From> for Element<'a, Message> +where + Message: 'static, +{ + fn from(scrollable: Scrollable<'a, Message>) -> Element<'a, Message> { + Element::new(scrollable) + } +} + +/// The local state of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State; + +impl State { + /// Creates a new [`State`] with the scrollbar located at the top. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + State::default() + } +} diff --git a/ios/src/widget/slider.rs b/ios/src/widget/slider.rs new file mode 100644 index 0000000000..9a1421119f --- /dev/null +++ b/ios/src/widget/slider.rs @@ -0,0 +1,173 @@ +//! Display an interactive selector of a single value from a range of values. +//! +//! A [`Slider`] has some local [`State`]. +//! +//! [`Slider`]: struct.Slider.html +//! [`State`]: struct.State.html +use crate::{Element, Length, Widget, Hasher, layout}; + +pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; + +use std::{ops::RangeInclusive, hash::Hash, rc::Rc}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`Slider`] will try to fill the horizontal space of its container. +/// +/// The [`Slider`] range of numeric values is generic and its step size defaults +/// to 1 unit. +/// +/// [`Slider`]: struct.Slider.html +/// +/// # Example +/// ``` +/// # use iced_web::{slider, Slider}; +/// # +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let state = &mut slider::State::new(); +/// let value = 50.0; +/// +/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); +/// ``` +/// +/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) +#[allow(missing_debug_implementations)] +pub struct Slider<'a, T, Message> { + _state: &'a mut State, + range: RangeInclusive, + step: T, + value: T, + on_change: Rc Message>>, + width: Length, + style: Box, +} + +impl<'a, T, Message> Slider<'a, T, Message> +where + T: Copy + From + std::cmp::PartialOrd, +{ + /// Creates a new [`Slider`]. + /// + /// It expects: + /// * the local [`State`] of the [`Slider`] + /// * an inclusive range of possible values + /// * the current value of the [`Slider`] + /// * a function that will be called when the [`Slider`] is dragged. + /// It receives the new value of the [`Slider`] and must produce a + /// `Message`. + /// + /// [`Slider`]: struct.Slider.html + /// [`State`]: struct.State.html + pub fn new( + state: &'a mut State, + range: RangeInclusive, + value: T, + on_change: F, + ) -> Self + where + F: 'static + Fn(T) -> Message, + { + let value = if value >= *range.start() { + value + } else { + *range.start() + }; + + let value = if value <= *range.end() { + value + } else { + *range.end() + }; + + Slider { + _state: state, + value, + range, + step: T::from(1), + on_change: Rc::new(Box::new(on_change)), + width: Length::Fill, + style: Default::default(), + } + } + + /// Sets the width of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the style of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn style(mut self, style: impl Into>) -> Self { + self.style = style.into(); + self + } + + /// Sets the step size of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn step(mut self, step: T) -> Self { + self.step = step; + self + } +} + +impl<'a, T, Message> Widget for Slider<'a, T, Message> +where + T: 'static + Copy + Into + num_traits::FromPrimitive, + Message: 'static, +{ + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, T, Message> From> for Element<'a, Message> +where + T: 'static + Copy + Into + num_traits::FromPrimitive, + Message: 'static, +{ + fn from(slider: Slider<'a, T, Message>) -> Element<'a, Message> { + Element::new(slider) + } +} + +/// The local state of a [`Slider`]. +/// +/// [`Slider`]: struct.Slider.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State; + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + Self + } +} diff --git a/ios/src/widget/space.rs b/ios/src/widget/space.rs new file mode 100644 index 0000000000..88cdbf7aba --- /dev/null +++ b/ios/src/widget/space.rs @@ -0,0 +1,70 @@ +use crate::{Element, Length, Widget, Hasher, layout}; +use std::hash::Hash; + +/// An amount of empty space. +/// +/// It can be useful if you want to fill some space with nothing. +#[derive(Debug)] +pub struct Space { + width: Length, + height: Length, +} + +impl Space { + /// Creates an amount of empty [`Space`] with the given width and height. + /// + /// [`Space`]: struct.Space.html + pub fn new(width: Length, height: Length) -> Self { + Space { width, height } + } + + /// Creates an amount of horizontal [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_width(width: Length) -> Self { + Space { + width, + height: Length::Shrink, + } + } + + /// Creates an amount of vertical [`Space`]. + /// + /// [`Space`]: struct.Space.html + pub fn with_height(height: Length) -> Self { + Space { + width: Length::Shrink, + height, + } + } +} + +impl<'a, Message> Widget for Space { + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } +} + +impl<'a, Message> From for Element<'a, Message> { + fn from(space: Space) -> Element<'a, Message> { + Element::new(space) + } +} diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index aca0d2e2a6..94f67f24b5 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -9,7 +9,14 @@ use crate::{ VerticalAlignment, Widget, WidgetPointers, - Hasher, + Hasher, layout, Size, +}; +use std::convert::TryInto; +use std::ffi::CString; +use uikit_sys::{ + CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, + NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, + UIView_UIViewGeometry, UIView_UIViewHierarchy, }; /// A paragraph of text. @@ -114,13 +121,6 @@ impl Text { self } } -use std::convert::TryInto; -use std::ffi::CString; -use uikit_sys::{ - CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, - NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, - UIView_UIViewGeometry, UIView_UIViewHierarchy, -}; impl<'a, Message> Widget for Text { @@ -134,7 +134,7 @@ impl<'a, Message> Widget for Text { self.height.hash(state); } - fn draw(&mut self, parent: UIView) -> WidgetPointers { + fn draw(&mut self, _parent: UIView) -> WidgetPointers { let label = unsafe { let label = UILabel::alloc(); @@ -149,16 +149,18 @@ impl<'a, Message> Widget for Text { ); label.init(); label.setText_(text.0); + /* let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, size: CGSize { - height: 20.0, - width: 500.0, + height: 0.0, + width: 0.0, }, }; + label.setFrame_(rect); + */ label.setAdjustsFontSizeToFitWidth_(true); label.setMinimumScaleFactor_(100.0); - label.setFrame_(rect); if let Some(color) = self.color { let background = UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( @@ -169,8 +171,7 @@ impl<'a, Message> Widget for Text { )); label.setTextColor_(background.0) } - label.setFrame_(rect); - parent.addSubview_(label.0); + //parent.addSubview_(label.0); label }; let hash = { @@ -186,6 +187,35 @@ impl<'a, Message> Widget for Text { hash, } } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + + let size = self.size.unwrap_or(0); + + let bounds = limits.max(); + todo!() + /* + + let (width, height) = + renderer.measure(&self.content, size, self.font, bounds); + + let size = limits.resolve(Size::new(width, height)); + + layout::Node::new(size) + */ + } + } impl<'a, Message> From for Element<'a, Message> { diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 282625dc75..2b19ed55db 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -1,11 +1,13 @@ - //! Display fields that can be filled with text. //! //! A [`TextInput`] has some local [`State`]. //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::{Element, Length, Widget, Hasher, WidgetPointers, event::{EventHandler, WidgetEvent}}; +use crate::{ + event::{EventHandler, WidgetEvent}, + layout, Element, Hasher, Length, Widget, WidgetPointers, +}; pub use iced_style::text_input::{Style, StyleSheet}; @@ -13,14 +15,10 @@ use std::{rc::Rc, u32}; use std::convert::TryInto; use uikit_sys::{ - id, CGPoint, CGRect, CGSize, - NSString, NSString_NSStringExtensionMethods, UIView, + id, CGPoint, CGRect, CGSize, INSNotificationCenter, INSObject, IUITextView, + NSNotificationCenter, NSString, NSString_NSStringExtensionMethods, + UITextView, UITextViewTextDidChangeNotification, UIView, UIView_UIViewHierarchy, - UITextView, - IUITextView, - NSNotificationCenter, - INSNotificationCenter, - UITextViewTextDidChangeNotification, }; /// A field that can be filled with text. @@ -169,27 +167,27 @@ where self.padding.hash(state); self.size.hash(state); } - fn draw(&mut self, parent: UIView) -> WidgetPointers { - let input_rect = CGRect { - origin: CGPoint { - x: 10.0, - y: 50.0 - }, + + fn draw(&mut self, _parent: UIView) -> WidgetPointers { + let _input_rect = CGRect { + origin: CGPoint { x: 10.0, y: 10.0 }, size: CGSize { - width: 200.0, - height: 200.0, - } + width: 100.0, + height: 100.0, + }, }; - let on_change = EventHandler::new(); - unsafe { + let textview = unsafe { let ui_textview = { - let foo = UITextView( - UITextView::alloc().initWithFrame_textContainer_( - input_rect, - 0 as id, - )); + let foo = UITextView(UITextView::alloc().init()); + /* + UITextView::alloc().initWithFrame_textContainer_( + input_rect, + 0 as id, + )); + */ foo }; + let on_change = EventHandler::new(ui_textview.0); /* input.addTarget_action_forControlEvents_( on_change.id, @@ -198,19 +196,24 @@ where ); */ // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc - let center = NSNotificationCenter(NSNotificationCenter::defaultCenter()); + let center = + NSNotificationCenter(NSNotificationCenter::defaultCenter()); + println!( + "TEXT INPUT SUBVIEW VALUE: {:?}, PARENT: {:?}", + ui_textview.0, _parent.0 + ); center.addObserver_selector_name_object_( on_change.id, sel!(sendEvent), UITextViewTextDidChangeNotification, - ui_textview.0 + ui_textview.0, ); - parent.addSubview_(ui_textview.0); - self.ui_textview = Some(ui_textview); - } + //parent.addSubview_(ui_textview.0); + ui_textview + }; - self.widget_id = on_change.widget_id; - debug!("drow TEXT UIVIEW {:?}", self.ui_textview.is_some()); + //self.widget_id = on_change.widget_id; + debug!("draw TEXT UIVIEW {:?}", self.ui_textview.is_some()); use std::hash::Hasher; let hash = { let mut hash = &mut crate::Hasher::default(); @@ -219,56 +222,65 @@ where }; WidgetPointers { - root: self.ui_textview.unwrap().0, + root: textview.0, others: Vec::new(), hash, } /* - use uikit_sys::{ - UISwitch, IUISwitch, - IUIControl, - }; - let input_rect = CGRect { - origin: CGPoint { - x: 10.0, - y: 10.0 - }, - size: CGSize { - width: 200.0, - height: 200.0, - } - }; - unsafe { - let switch = UISwitch( - IUISwitch::initWithFrame_( - UISwitch::alloc(), - input_rect, - ) - ); + use uikit_sys::{ + UISwitch, IUISwitch, + IUIControl, + }; + let input_rect = CGRect { + origin: CGPoint { + x: 10.0, + y: 10.0 + }, + size: CGSize { + width: 200.0, + height: 200.0, + } + }; + unsafe { + let switch = UISwitch( + IUISwitch::initWithFrame_( + UISwitch::alloc(), + input_rect, + ) + ); - /* - switch.addTarget_action_forControlEvents_( - on_change.id, - sel!(sendEvent), - uikit_sys::UIControlEvents_UIControlEventValueChanged, - ); - */ + /* + switch.addTarget_action_forControlEvents_( + on_change.id, + sel!(sendEvent), + uikit_sys::UIControlEvents_UIControlEventValueChanged, + ); + */ - parent.addSubview_(switch.0); - } - */ + parent.addSubview_(switch.0); + } + */ } - fn on_widget_event(&mut self, widget_event: WidgetEvent, messages: &mut Vec, widget_pointers: &WidgetPointers) { - let ui_textview = UITextView(widget_pointers.root); + fn on_widget_event( + &mut self, + widget_event: WidgetEvent, + messages: &mut Vec, + widget_pointers: &WidgetPointers, + ) { + debug!("on_widget_event for text input: widget_event.id: {:x}", widget_event.id); + let ui_textview = UITextView(widget_event.id as id); let value = unsafe { let value = NSString(ui_textview.text()); - let len = value.lengthOfBytesUsingEncoding_( - uikit_sys::NSUTF8StringEncoding, - ); + let len = value + .lengthOfBytesUsingEncoding_(uikit_sys::NSUTF8StringEncoding); let bytes = value.UTF8String() as *const u8; - String::from_utf8(std::slice::from_raw_parts(bytes, len.try_into().unwrap()).to_vec()).unwrap() + String::from_utf8( + std::slice::from_raw_parts(bytes, len.try_into().unwrap()) + .to_vec(), + ) + .unwrap() }; self.value = value; @@ -293,6 +305,18 @@ where } */ } + + fn layout(&self, limits: &layout::Limits) -> layout::Node { + todo!(); + } + + fn width(&self) -> Length { + todo!(); + } + + fn height(&self) -> Length { + todo!(); + } } impl<'a, Message> From> for Element<'a, Message> From 59c7c1d4ae4e93a9152f31a5d4ce620f6dbe6cc5 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Wed, 8 Jul 2020 13:45:25 -0700 Subject: [PATCH 17/33] updates from merge --- src/window/icon.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/window/icon.rs b/src/window/icon.rs index 15e0312d2d..396a04eac2 100644 --- a/src/window/icon.rs +++ b/src/window/icon.rs @@ -3,18 +3,18 @@ use std::fmt; use std::io; /// The icon of a window. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] #[derive(Debug, Clone)] pub struct Icon(iced_winit::winit::window::Icon); /// The icon of a window. -#[cfg(target_arch = "wasm32")] +#[cfg(any(target_arch = "wasm32", target_os = "ios"))] #[derive(Debug, Clone)] pub struct Icon; impl Icon { /// Creates an icon from 32bpp RGBA data. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] pub fn from_rgba( rgba: Vec, width: u32, @@ -27,7 +27,7 @@ impl Icon { } /// Creates an icon from 32bpp RGBA data. - #[cfg(target_arch = "wasm32")] + #[cfg(any(target_arch = "wasm32", target_os = "ios"))] pub fn from_rgba( _rgba: Vec, _width: u32, @@ -62,7 +62,7 @@ pub enum Error { OsError(io::Error), } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From for Error { fn from(error: iced_winit::winit::window::BadIcon) -> Self { use iced_winit::winit::window::BadIcon; @@ -86,7 +86,7 @@ impl From for Error { } } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From for iced_winit::winit::window::Icon { fn from(icon: Icon) -> Self { icon.0 From bd22844abf5c9dfb8578d531cea5415bce169079 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Fri, 10 Jul 2020 19:25:42 -0700 Subject: [PATCH 18/33] Got widget events to propigate for columns --- examples/ios-example/src/main.rs | 40 +++++-- ios/src/application.rs | 41 ++++--- ios/src/event.rs | 13 ++- ios/src/lib.rs | 2 +- ios/src/widget.rs | 143 ++++++++++++++++++----- ios/src/widget/column.rs | 175 +++++++++++++++++----------- ios/src/widget/text.rs | 105 +++++++++-------- ios/src/widget/text_input.rs | 194 ++++++++++++++----------------- 8 files changed, 433 insertions(+), 280 deletions(-) diff --git a/examples/ios-example/src/main.rs b/examples/ios-example/src/main.rs index 107db789b4..5eae5923bc 100644 --- a/examples/ios-example/src/main.rs +++ b/examples/ios-example/src/main.rs @@ -12,9 +12,9 @@ use iced::{ Text, TextInput, text_input, - Container, + //Container, Color, - Checkbox, + //Checkbox, }; pub fn main() { color_backtrace::install(); @@ -31,12 +31,14 @@ pub struct Simple { toggle: bool, text: String, text_state: text_input::State, + text_state2: text_input::State, } #[derive(Debug, Clone)] pub enum Message { //EventOccurred(iced_native::Event), Toggled(bool), TextUpdated(String), + TextSubmit, } impl Sandbox for Simple { @@ -65,19 +67,39 @@ impl Sandbox for Simple { debug!("RERUNNING VIEW : {:#?}", self); let column = Column::new() .push( - TextInput::new( - &mut self.text_state, - "", - "", - |s| { Message::TextUpdated(s) } + Column::new() + //.push( + // Text::new(String::from("FIRST FIRST")).color(Color::BLACK) + //) + .push( + TextInput::new( + &mut self.text_state, + "", + "", + |s| { + debug!("The 1st text box has \"{}\" in it!", s); + Message::TextUpdated(s) + } + ).on_submit(Message::TextSubmit), ) ) .push( - Text::new(&self.text).color(Color::BLACK) + Text::new(String::from("SECOND SECOND")).color(Color::BLACK) ) .push( - Text::new(String::from("foo foo foo")).color(Color::BLACK) + TextInput::new( + &mut self.text_state2, + "", + "", + |s| { + debug!("The 2nd text box has \"{}\" in it!", s); + Message::TextUpdated(s) + } + ) ) + //.push( + // Text::new(&self.text).color(Color::BLACK) + //) ; column.into() diff --git a/ios/src/application.rs b/ios/src/application.rs index 0f3bfb6deb..22eebe7da5 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,7 +1,9 @@ use crate::{ event::{EventHandler, WidgetEvent}, - WidgetPointers, - widget::Widget, + widget::{ + Widget, + WidgetNode, + }, Command, Element, Executor, Runtime, Subscription, }; use std::hash::Hasher; @@ -22,9 +24,10 @@ use uikit_sys::{ //IUISwitch, //IUIView, UIColor, - //UIView_UIViewGeometry, + UIView_UIViewGeometry, //UISwitch, UIView, + CGPoint, CGRect, CGSize, //UIView_UIViewHierarchy, //UIView, //UIViewController, @@ -131,8 +134,8 @@ pub trait Application: Sized { window_builder = window_builder .with_title(title) .with_maximized(true) - .with_fullscreen(None) - //.with_inner_size(winit::dpi::LogicalSize { width: 100, height: 100}) + //.with_fullscreen(None) + //.with_inner_size(winit::dpi::LogicalSize { width: 1000, height: 1000}) ; /* .with_resizable(settings.window.resizable) @@ -146,15 +149,20 @@ pub trait Application: Sized { let background = UIColor(UIColor::greenColor()); //let background = UIColor(UIColor::whiteColor()); root_view.setBackgroundColor_(background.0); + /* + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 400.0, + width: 300.0, + }, + }; + root_view.setFrame_(rect); + */ } //proxy.send_event(WidgetEvent {widget_id: 0} ); let mut cached_hash: u64 = 0; - let mut _current_element: Option> = None; - let mut widget_pointers = WidgetPointers { - root: 0 as id, - others: Vec::new(), - hash: cached_hash, - }; + let mut widget_tree : Option = None; event_loop.run( move |event: winit::event::Event, _, control_flow| { @@ -165,9 +173,11 @@ pub trait Application: Sized { event::Event::MainEventsCleared => {} event::Event::UserEvent(widget_event) => { let mut element = app.view(); - element + if let Some(ref widget_tree) = widget_tree { + element .widget - .on_widget_event(widget_event, &mut messages, &widget_pointers); + .on_widget_event(widget_event, &mut messages, &widget_tree); + } let hash = { let mut hash = &mut crate::Hasher::default(); @@ -176,7 +186,7 @@ pub trait Application: Sized { }; if hash != cached_hash { cached_hash = hash; - widget_pointers = element.draw(root_view); + widget_tree = Some(element.update_or_add(Some(root_view), widget_tree.take())); } } event::Event::RedrawRequested(_) => { @@ -194,9 +204,10 @@ pub trait Application: Sized { element.widget.hash_layout(&mut hash); hash.finish() }; + //widget_tree = Some(element.update_or_add(root_view, widget_tree)); if hash != cached_hash { cached_hash = hash; - widget_pointers = element.draw(root_view); + widget_tree = Some(element.update_or_add(Some(root_view), widget_tree.take())); } } _ => { diff --git a/ios/src/event.rs b/ios/src/event.rs index 8432cf3ff0..42a9ede431 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -25,11 +25,11 @@ pub struct EventHandler { } static mut PROXY : Option> = None; -static mut COUNTER: Option = None; +static mut COUNTER: Option = None; impl EventHandler { pub fn init(proxy: EventLoopProxy) { unsafe { - COUNTER = Some(AtomicU64::new(0)); + COUNTER = Some(0); PROXY = Some(proxy); } } @@ -40,13 +40,18 @@ impl EventHandler { let obj: id = objc::msg_send![Self::class(), alloc]; let obj: id = objc::msg_send![obj, init]; - if let Some(counter) = &COUNTER { - widget_id = counter.fetch_add(0, Ordering::Relaxed); + if let Some(mut counter) = COUNTER { + counter += 1; + COUNTER = Some(counter); + debug!("WHAT THE FUCK IS THE COUNTER: {:?}!", counter); + widget_id = counter; + //widget_id = counter.fetch_add(0, Ordering::SeqCst); (*obj).set_ivar::("widget_id", widget_id); (*obj).set_ivar::("objc_id", objc_id); } obj }; + debug!("NEW EVENTHANDLER WITH WIDGET ID :{:?}", widget_id); Self{id: obj, widget_id} } extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) diff --git a/ios/src/lib.rs b/ios/src/lib.rs index 1f8d79e8d8..ff703cc3b2 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -10,7 +10,7 @@ pub mod widget; pub mod keyboard; pub mod mouse; pub use widget::{ - Element, Widget, Text, TextInput, WidgetPointers, Column, + Element, Widget, Text, TextInput, Column, }; use event::WidgetEvent; mod layout; diff --git a/ios/src/widget.rs b/ios/src/widget.rs index ecf871228e..57db4e8ca2 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -11,6 +11,7 @@ use uikit_sys::{ id, }; +/* pub mod button; pub mod checkbox; pub mod container; @@ -19,62 +20,142 @@ pub mod progress_bar; pub mod radio; pub mod scrollable; pub mod slider; +mod row; +mod space; +*/ pub mod text_input; mod column; -mod row; -mod space; mod text; +/* #[doc(no_inline)] pub use button::Button; #[doc(no_inline)] pub use scrollable::Scrollable; #[doc(no_inline)] pub use slider::Slider; +*/ #[doc(no_inline)] pub use text::Text; #[doc(no_inline)] pub use text_input::TextInput; -pub use checkbox::Checkbox; pub use column::Column; +/* +pub use checkbox::Checkbox; pub use container::Container; pub use image::Image; pub use progress_bar::ProgressBar; pub use radio::Radio; pub use row::Row; pub use space::Space; +*/ -pub struct WidgetPointers { - pub root: id, - pub others: Vec, - pub hash: u64, - //child: Option>, +#[derive(Debug, PartialEq)] +pub enum WidgetType { + BaseElement, + Button, + Scrollable, + Slider, + Text, + TextInput, + Checkbox, + Column, + Container, + Image, + ProgressBar, + Radio, + Row, + Space, } -/* -impl Drop for WidgetPointers { +use std::rc::Rc; +use std::cell::RefCell; + +pub struct WidgetNode { + pub (crate) view_id: id, + //pub (crate) widget_id: u64, + + // Used for memory collection. + related_ids: Vec, + pub widget_type: WidgetType, + // Used in things like Row, Column and Container. + pub (crate) children: Vec>>, +} + +impl Default for WidgetNode { + fn default() -> Self { + Self { + view_id: 0 as id, + //widget_id: 0, + related_ids: Vec::new(), + widget_type: WidgetType::BaseElement, + children: Vec::new(), + } + } +} + +impl Drop for WidgetNode { fn drop(&mut self) { - use uikit_sys::UIView_UIViewHierarchy; + /* + debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); + use uikit_sys::{ + UIView_UIViewHierarchy, + NSObject, INSObject, + }; unsafe { - let root = UIView(self.root); - root.removeFromSuperview(); + let view = UIView(self.view_id); + view.removeFromSuperview(); + view.dealloc(); + } + for i in &self.related_ids { + let obj = NSObject(*i); + unsafe { + obj.dealloc(); + } } + for i in &self.children { + drop(i); + } + */ + } +} + +impl WidgetNode { + pub fn new(view_id: id, widget_type: WidgetType) -> Self { + Self { + view_id, + related_ids: Vec::new(), + widget_type, + children: Vec::new(), + } + } + pub fn add_related_id(&mut self, related_id: id) { + self.related_ids.push(related_id); + } + + pub fn add_child(&mut self, child: WidgetNode) { + self.children.push(Rc::new(RefCell::new(child))); + } + pub fn add_children(&mut self, _children: Vec) { + /* + for i in &children { + self.add_child(*i); + } + */ } } -*/ pub trait Widget { - fn draw( + fn update_or_add( &mut self, - parent: UIView, - ) -> WidgetPointers { - WidgetPointers { - root: parent.0, - others: Vec::new(), - hash: 0, - //child: None - } + _parent: Option, + _old_node: Option, + ) -> WidgetNode { + unimplemented!("USING BASE IMPLEMENTATION FOR UPDATE_OR_ADD"); + } + fn get_widget_type(&self) -> WidgetType { + unimplemented!("USING BASE IMPLEMENTATION for GET_WIDGET_TYPE"); } fn hash_layout(&self, state: &mut Hasher); fn on_widget_event( @@ -83,10 +164,11 @@ pub trait Widget { //_layout: Layout<'_>, //_cursor_position: Point, _messages: &mut Vec, - _widget_pointers: &WidgetPointers, + _widget_node: &WidgetNode, //_renderer: &Renderer, //_clipboard: Option<&dyn Clipboard>, ) { + debug!("on_widget_event for {:?}", self.get_widget_type()); } fn layout( &self, @@ -137,19 +219,24 @@ impl<'a, Message> Widget for Element<'a, Message> todo!(); } - fn draw(&mut self, parent: UIView) -> WidgetPointers { - self.widget.draw(parent) + fn update_or_add(&mut self, parent: Option, old_node: Option) -> WidgetNode { + self.widget.update_or_add(parent, old_node) } + + fn get_widget_type(&self) -> WidgetType { + self.widget.get_widget_type() + } + fn on_widget_event( &mut self, event: WidgetEvent, //_layout: Layout<'_>, //_cursor_position: Point, messages: &mut Vec, - widget_pointers: &WidgetPointers, + widget_node: &WidgetNode, //_renderer: &Renderer, //_clipboard: Option<&dyn Clipboard>, ) { - self.widget.on_widget_event(event, messages, widget_pointers); + self.widget.on_widget_event(event, messages, widget_node); } } diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 99a7a1bd6a..29afc0bae0 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -1,7 +1,11 @@ -use crate::{Align, event::WidgetEvent, Element, Length, Widget, Hasher, layout, WidgetPointers}; +use crate::{ + event::WidgetEvent, + layout, + widget::{WidgetNode, WidgetType}, + Align, Element, Hasher, Length, Widget, +}; use std::hash::Hash; - use std::u32; /// A container that distributes its contents vertically. @@ -115,29 +119,14 @@ impl<'a, Message> Column<'a, Message> { } } use uikit_sys::{ - UIView, - id, - UIStackView, - IUIStackView, - UIView_UIViewRendering, - CGRect, CGPoint, CGSize, - INSObject, - UIColor, IUIColor, - UIView_UIViewHierarchy, - UIView_UIViewGeometry, - - NSLayoutDimension, - INSLayoutDimension, - NSLayoutConstraint, - INSLayoutConstraint, - UIView_UIViewLayoutConstraintCreation, - UIStackViewDistribution_UIStackViewDistributionFill, - UITextView, - IUITextView, - - - UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + id, CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, + INSObject, IUIColor, IUIStackView, IUITextView, NSLayoutConstraint, + NSLayoutDimension, UIColor, + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, UIStackViewAlignment_UIStackViewAlignmentCenter, + UIStackViewDistribution_UIStackViewDistributionFill, UITextView, UIView, + UIView_UIViewGeometry, UIView_UIViewHierarchy, + UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, }; impl<'a, Message> Widget for Column<'a, Message> @@ -148,16 +137,15 @@ where &mut self, event: WidgetEvent, //_layout: Layout<'_>, - //_cursor_position: Point, messages: &mut Vec, - widget_pointers: &WidgetPointers, - //_renderer: &Renderer, - //_clipboard: Option<&dyn Clipboard>, + widget_node: &WidgetNode, ) { - debug!("on_widget_event for column"); - for i in &mut self.children { - debug!("on_widget_event for child!"); - i.on_widget_event(event.clone(), messages, widget_pointers); + debug!("on_widget_event for column for {:?} children", self.children.len()); + for (i, node) in + &mut self.children.iter_mut().zip(widget_node.children.iter()) + { + debug!("on_widget_event for {:?} child", i.get_widget_type()); + i.on_widget_event(event.clone(), messages, &node.borrow()); } } fn hash_layout(&self, state: &mut Hasher) { @@ -175,10 +163,7 @@ where child.widget.hash_layout(state); } } - fn layout( - &self, - limits: &layout::Limits, - ) -> layout::Node { + fn layout(&self, limits: &layout::Limits) -> layout::Node { let limits = limits .max_width(self.max_width) .max_height(self.max_height) @@ -202,30 +187,32 @@ where self.height } - fn draw(&mut self, parent: UIView) -> WidgetPointers { + fn get_widget_type(&self) -> WidgetType { + WidgetType::Column + } + + fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { let stack_view = unsafe { let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, size: CGSize { height: 400.0, - width: 400.0, + width: 300.0, }, }; - let stack_view = UIStackView(UIStackView::alloc().initWithFrame_(rect)); + let stack_view = + UIStackView(UIStackView::alloc().initWithFrame_(rect)); //let stack_view = UIStackView(UIStackView::alloc().init()); //stack_view.setFrame_(rect); - stack_view.setAxis_(UILayoutConstraintAxis_UILayoutConstraintAxisVertical); - stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); - stack_view.setDistribution_(UIStackViewDistribution_UIStackViewDistributionFill); - stack_view.setSpacing_(10.0); - for i in &mut self.children { - let subview = UIView(i.draw(UIView(0 as id)).root); - debug!("SUBVIEW VALUE: {:?}, PARENT: {:?}", subview.0, parent.0); - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - stack_view.addArrangedSubview_(subview.0); + stack_view.setAxis_( + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + ); + //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setDistribution_( + UIStackViewDistribution_UIStackViewDistributionFill, + ); + if let Some(parent) = parent { + parent.addSubview_(stack_view.0); } /* @@ -235,7 +222,77 @@ where let layout = NSLayoutDimension(view3.widthAnchor()); NSLayoutConstraint(layout.constraintEqualToConstant_(120.0)).setActive_(true); stack_view.addArrangedSubview_(view3.0); + */ + + + stack_view + }; + let mut stackview_node = + WidgetNode::new(stack_view.0, WidgetType::Column); + for i in &mut self.children { + let node = i.update_or_add(None, None); + let subview = UIView(node.view_id); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + } + /* + if let Some(old_node) = old_node { + if self.children.len() != old_node.children.len() { + for i in &mut self.children { + let node = i.update_or_add(None, None); + let subview = UIView(node.view_id); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + } + } else { + for (i, node) in + self.children.iter_mut().zip(old_node.children.iter()) + { + if i.get_widget_type() == node.borrow().widget_type { + } else { + unsafe { + let view = UIView(node.borrow().view_id); + //view.removeFromSuperview(); + stack_view.removeArrangedSubview_(node.borrow().view_id); + } + let node = i.update_or_add(None, Some(node.borrow())); + let subview = UIView(node.view_id); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + } + } + } + } else { + */ + //} + /* + unsafe { + stack_view.setSpacing_(10.0); let view1 = UIView(UIView::alloc().init()); view1.setBackgroundColor_(UIColor::redColor()); let layout = NSLayoutDimension(view1.heightAnchor()); @@ -252,21 +309,9 @@ where let layout = NSLayoutDimension(view2.widthAnchor()); NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); stack_view.addArrangedSubview_(view2.0); - */ - - parent.addSubview_(stack_view.0); - - //let background = UIColor(UIColor::greenColor()); - //let background = UIColor(UIColor::blackColor()); - //stack_view.setBackgroundColor_(background.0); - stack_view - }; - WidgetPointers { - root: stack_view.0, - others: Vec::new(), - hash: 0, - //children: None } + */ + stackview_node } } diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 94f67f24b5..03421c8f8c 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -8,7 +8,7 @@ use crate::{ Length, VerticalAlignment, Widget, - WidgetPointers, + widget::{WidgetNode, WidgetType}, Hasher, layout, Size, }; use std::convert::TryInto; @@ -29,7 +29,6 @@ use uikit_sys::{ /// Text::new("I <3 iced!") /// .size(40); /// ``` -#[derive(Debug, Clone)] pub struct Text { content: String, size: Option, @@ -133,59 +132,59 @@ impl<'a, Message> Widget for Text { self.width.hash(state); self.height.hash(state); } + fn get_widget_type(&self) -> WidgetType { + WidgetType::Text + } - fn draw(&mut self, _parent: UIView) -> WidgetPointers { - - let label = unsafe { - let label = UILabel::alloc(); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) + fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { + if let Some(_old_node) = old_node { + unimplemented!("Update this text with new text maybe"); + //WidgetNode::default() + } else { + + let label = unsafe { + let label = UILabel::alloc(); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) .expect("CString::new failed") .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.init(); - label.setText_(text.0); - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 0.0, - width: 0.0, - }, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.init(); + label.setText_(text.0); + /* + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 0.0, + width: 0.0, + }, + }; + label.setFrame_(rect); + */ + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(10.0); + if let Some(color) = self.color { + let background = + UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + )); + label.setTextColor_(background.0) + } + if let Some(parent) = parent { + parent.addSubview_(label.0); + } + label }; - label.setFrame_(rect); - */ - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(100.0); - if let Some(color) = self.color { - let background = - UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - )); - label.setTextColor_(background.0) - } - //parent.addSubview_(label.0); - label - }; - let hash = { - let mut hash = &mut crate::Hasher::default(); - //self.hash_layout(&mut hash); - use std::hash::Hasher; - hash.finish() - }; - - WidgetPointers { - root: label.0, - others: Vec::new(), - hash, + WidgetNode::new(label.0, WidgetType::Text) } + } fn width(&self) -> Length { self.width @@ -197,15 +196,15 @@ impl<'a, Message> Widget for Text { fn layout( &self, - limits: &layout::Limits, + _limits: &layout::Limits, ) -> layout::Node { + todo!() + /* let limits = limits.width(self.width).height(self.height); let size = self.size.unwrap_or(0); let bounds = limits.max(); - todo!() - /* let (width, height) = renderer.measure(&self.content, size, self.font, bounds); diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 2b19ed55db..16b10b342f 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -6,7 +6,11 @@ //! [`State`]: struct.State.html use crate::{ event::{EventHandler, WidgetEvent}, - layout, Element, Hasher, Length, Widget, WidgetPointers, + layout, Element, Hasher, Length, Widget, + widget::{ + WidgetType, + WidgetNode, + }, }; pub use iced_style::text_input::{Style, StyleSheet}; @@ -55,7 +59,6 @@ pub struct TextInput<'a, Message> { on_submit: Option, style_sheet: Box, widget_id: u64, - pub ui_textview: Option, } impl<'a, Message> TextInput<'a, Message> { @@ -78,6 +81,7 @@ impl<'a, Message> TextInput<'a, Message> { where F: 'static + Fn(String) -> Message, { + debug!("CREATING NEW TEXT INPUT"); Self { _state: state, placeholder: String::from(placeholder), @@ -91,7 +95,6 @@ impl<'a, Message> TextInput<'a, Message> { on_submit: None, style_sheet: Default::default(), widget_id: 0, - ui_textview: None, } } @@ -167,124 +170,105 @@ where self.padding.hash(state); self.size.hash(state); } + fn get_widget_type(&self) -> WidgetType { + WidgetType::TextInput + } - fn draw(&mut self, _parent: UIView) -> WidgetPointers { - let _input_rect = CGRect { - origin: CGPoint { x: 10.0, y: 10.0 }, - size: CGSize { - width: 100.0, - height: 100.0, - }, - }; - let textview = unsafe { - let ui_textview = { - let foo = UITextView(UITextView::alloc().init()); + fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { + if let Some(old_node) = old_node { + old_node + } else { + let textview = unsafe { + let ui_textview = { + if parent.is_none() { + UITextView(UITextView::alloc().init()) + } else { + let input_rect = CGRect { + origin: CGPoint { x: 10.0, y: 10.0 }, + size: CGSize { + width: 100.0, + height: 100.0, + }, + }; + UITextView(UITextView::alloc().initWithFrame_textContainer_( + input_rect, + 0 as id, + )) + } + }; + let on_change = EventHandler::new(ui_textview.0); + self.widget_id = on_change.widget_id; /* - UITextView::alloc().initWithFrame_textContainer_( - input_rect, - 0 as id, - )); - */ - foo + input.addTarget_action_forControlEvents_( + on_change.id, + sel!(sendEvent), + uikit_sys::UIControlEvents_UIControlEventValueChanged, + ); + */ + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc + let center = + NSNotificationCenter(NSNotificationCenter::defaultCenter()); + center.addObserver_selector_name_object_( + on_change.id, + sel!(sendEvent), + UITextViewTextDidChangeNotification, + ui_textview.0, + ); + //parent.addSubview_(ui_textview.0); + if let Some(parent) = parent { + parent.addSubview_(ui_textview.0); + } + ui_textview }; - let on_change = EventHandler::new(ui_textview.0); - /* - input.addTarget_action_forControlEvents_( - on_change.id, - sel!(sendEvent), - uikit_sys::UIControlEvents_UIControlEventValueChanged, - ); - */ - // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc - let center = - NSNotificationCenter(NSNotificationCenter::defaultCenter()); - println!( - "TEXT INPUT SUBVIEW VALUE: {:?}, PARENT: {:?}", - ui_textview.0, _parent.0 - ); - center.addObserver_selector_name_object_( - on_change.id, - sel!(sendEvent), - UITextViewTextDidChangeNotification, - ui_textview.0, - ); - //parent.addSubview_(ui_textview.0); - ui_textview - }; - - //self.widget_id = on_change.widget_id; - debug!("draw TEXT UIVIEW {:?}", self.ui_textview.is_some()); - use std::hash::Hasher; - let hash = { - let mut hash = &mut crate::Hasher::default(); - self.hash_layout(&mut hash); - hash.finish() - }; - - WidgetPointers { - root: textview.0, - others: Vec::new(), - hash, - } - /* - use uikit_sys::{ - UISwitch, IUISwitch, - IUIControl, - }; - let input_rect = CGRect { - origin: CGPoint { - x: 10.0, - y: 10.0 - }, - size: CGSize { - width: 200.0, - height: 200.0, - } + use std::hash::Hasher; + let hash = { + let mut hash = &mut crate::Hasher::default(); + self.hash_layout(&mut hash); + hash.finish() }; - unsafe { - let switch = UISwitch( - IUISwitch::initWithFrame_( - UISwitch::alloc(), - input_rect, - ) - ); - /* - switch.addTarget_action_forControlEvents_( - on_change.id, - sel!(sendEvent), - uikit_sys::UIControlEvents_UIControlEventValueChanged, - ); - */ + WidgetNode::new(textview.0, WidgetType::TextInput) + } - parent.addSubview_(switch.0); - } - */ } fn on_widget_event( &mut self, widget_event: WidgetEvent, messages: &mut Vec, - widget_pointers: &WidgetPointers, + widget_node: &WidgetNode, ) { - debug!("on_widget_event for text input: widget_event.id: {:x}", widget_event.id); - let ui_textview = UITextView(widget_event.id as id); - let value = unsafe { - let value = NSString(ui_textview.text()); - let len = value - .lengthOfBytesUsingEncoding_(uikit_sys::NSUTF8StringEncoding); - let bytes = value.UTF8String() as *const u8; - String::from_utf8( - std::slice::from_raw_parts(bytes, len.try_into().unwrap()) + debug!( + "on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, self.widget_id: {:?} widget_node.view_id {:?}", + widget_event.id, + widget_event.widget_id, + self.widget_id, + widget_node.view_id, + ); + if widget_event.id as id == widget_node.view_id { + let ui_textview = UITextView(widget_event.id as id); + let value = unsafe { + let value = NSString(ui_textview.text()); + let len = value + .lengthOfBytesUsingEncoding_(uikit_sys::NSUTF8StringEncoding); + let bytes = value.UTF8String() as *const u8; + String::from_utf8( + std::slice::from_raw_parts(bytes, len.try_into().unwrap()) .to_vec(), - ) - .unwrap() - }; - self.value = value; + ) + .unwrap() + }; + if value.ends_with("\n") { + if let Some(on_submit) = self.on_submit.take() { + messages.push(on_submit); + } + } else { + self.value = value; - messages.push((self.on_change)(self.value.clone())); + messages.push((self.on_change)(self.value.clone())); + } + } /* debug!("on_widget_event TEXT UIVIEW {:?}", self.ui_textview.is_some()); From cdbc3c612f1d74042b567693e90b63e40c7008dd Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Mon, 13 Jul 2020 12:35:50 -0700 Subject: [PATCH 19/33] moar updates --- Cargo.toml | 1 - examples/ios-example/Cargo.toml | 28 --- ios/Cargo.toml | 18 ++ ios/Makefile | 15 ++ .../main.rs => ios/examples/ios-example.rs | 64 +++--- ios/src/application.rs | 74 ++++--- ios/src/event.rs | 1 - ios/src/layout.rs | 68 ------ ios/src/layout/DRUID_LICENSE | 202 ------------------ ios/src/layout/debugger.rs | 26 --- ios/src/layout/flex.rs | 175 --------------- ios/src/layout/limits.rs | 200 ----------------- ios/src/layout/node.rs | 94 -------- ios/src/lib.rs | 2 - ios/src/widget.rs | 102 ++++++--- ios/src/widget/column.rs | 171 +++++++++------ ios/src/widget/text.rs | 188 ++++++++++------ ios/src/widget/text_input.rs | 26 +-- 18 files changed, 419 insertions(+), 1036 deletions(-) delete mode 100644 examples/ios-example/Cargo.toml create mode 100644 ios/Makefile rename examples/ios-example/src/main.rs => ios/examples/ios-example.rs (60%) delete mode 100644 ios/src/layout.rs delete mode 100644 ios/src/layout/DRUID_LICENSE delete mode 100644 ios/src/layout/debugger.rs delete mode 100644 ios/src/layout/flex.rs delete mode 100644 ios/src/layout/limits.rs delete mode 100644 ios/src/layout/node.rs diff --git a/Cargo.toml b/Cargo.toml index 31cfd850ec..06c10e49f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ members = [ "style", "web", "ios", - "examples/ios-example", "wgpu", "winit", "examples/bezier_tool", diff --git a/examples/ios-example/Cargo.toml b/examples/ios-example/Cargo.toml deleted file mode 100644 index da90c1bafd..0000000000 --- a/examples/ios-example/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "ios-example" -version = "0.1.0" -authors = ["Sebastian Imlay "] -edition = "2018" -description = "" - -[package.metadata.bundle] -name = "ios-example" -identifier = "com.github.iced.simple" -category = "Utility" -short_description = "An example of a bundled application" -long_description = """ -A trivial application that just displays a blank window with -a title bar. It serves as an example of an application that -can be bundled with cargo-bundle, as well as a test-case for -cargo-bundle's support for bundling crate examples. -""" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -iced = { path = "../..", features = ["image", "debug"] } -log = "0.4" -pretty_env_logger = "0.3" -color-backtrace = { version = "0.4.2", features = ["failure-bt"] } - -#env_logger = "0.7" diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 67335885c5..f6c5326348 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -3,6 +3,19 @@ name = "iced_ios" version = "0.1.0" authors = ["Sebastian Imlay "] edition = "2018" +description = "iOS backend for iced" + +[package.metadata.bundle.example.ios-example] +name = "ios-example" +identifier = "com.github.iced.simple" +category = "Utility" +short_description = "An example of a bundled application" +long_description = """ +A trivial application that just displays a blank window with +a title bar. It serves as an example of an application that +can be bundled with cargo-bundle, as well as a test-case for +cargo-bundle's support for bundling crate examples. +""" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -28,3 +41,8 @@ path = "../style" version = "0.1" path = "../futures" features = ["thread-pool"] + +[dev-dependencies] +iced = { path = "../", features = ["image", "debug"] } +pretty_env_logger = "0.3" +color-backtrace = { version = "0.4.2", features = ["failure-bt"] } diff --git a/ios/Makefile b/ios/Makefile new file mode 100644 index 0000000000..1e4387d3d2 --- /dev/null +++ b/ios/Makefile @@ -0,0 +1,15 @@ +.PHONY: test example + +example: example-run + +example-build: + RUST_BACKTRACE=full RUST_LOG=debug cargo bundle --format ios --target x86_64-apple-ios --example ios-example + +example-install: example-build + xcrun simctl install booted $(PWD)/../target/x86_64-apple-ios/debug/examples/bundle/ios/ios-example.app/ + +example-run: example-install + xcrun simctl launch --console booted com.github.iced.simple + +test: + cargo dinghy --platform auto-ios-x86_64 test diff --git a/examples/ios-example/src/main.rs b/ios/examples/ios-example.rs similarity index 60% rename from examples/ios-example/src/main.rs rename to ios/examples/ios-example.rs index 5eae5923bc..9aafddbd7c 100644 --- a/examples/ios-example/src/main.rs +++ b/ios/examples/ios-example.rs @@ -65,43 +65,47 @@ impl Sandbox for Simple { fn view(&mut self) -> Element { debug!("RERUNNING VIEW : {:#?}", self); - let column = Column::new() - .push( - Column::new() - //.push( - // Text::new(String::from("FIRST FIRST")).color(Color::BLACK) - //) + if self.text.starts_with("A") { + debug!("RETURNING TEXT ONLY VIEW NOW"); + Text::new("THE TEXT STARTS WITH THE LETTER A").into() + } else { + let column = Column::new() + .push( + Column::new() + //.push( + // Text::new(String::from("FIRST FIRST")).color(Color::BLACK) + //) + .push( + TextInput::new( + &mut self.text_state, + "", + "", + |s| { + debug!("The 1st text box has \"{}\" in it!", s); + Message::TextUpdated(s) + } + ).on_submit(Message::TextSubmit), + ) + ) + .push( + Text::new(String::from("SECOND SECOND")).color(Color::BLACK) + ) .push( TextInput::new( - &mut self.text_state, + &mut self.text_state2, "", "", |s| { - debug!("The 1st text box has \"{}\" in it!", s); + debug!("The 2nd text box has \"{}\" in it!", s); Message::TextUpdated(s) } - ).on_submit(Message::TextSubmit), + ) ) - ) - .push( - Text::new(String::from("SECOND SECOND")).color(Color::BLACK) - ) - .push( - TextInput::new( - &mut self.text_state2, - "", - "", - |s| { - debug!("The 2nd text box has \"{}\" in it!", s); - Message::TextUpdated(s) - } - ) - ) - //.push( - // Text::new(&self.text).color(Color::BLACK) - //) - ; - column.into() - + //.push( + // Text::new(&self.text).color(Color::BLACK) + //) + ; + column.into() + } } } diff --git a/ios/src/application.rs b/ios/src/application.rs index 22eebe7da5..10305acb2c 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,12 +1,8 @@ use crate::{ event::{EventHandler, WidgetEvent}, - widget::{ - Widget, - WidgetNode, - }, + widget::{RenderAction, Widget, WidgetNode}, Command, Element, Executor, Runtime, Subscription, }; -use std::hash::Hasher; use winit::{ event, event_loop::{ControlFlow, EventLoop}, @@ -24,10 +20,9 @@ use uikit_sys::{ //IUISwitch, //IUIView, UIColor, - UIView_UIViewGeometry, //UISwitch, UIView, - CGPoint, CGRect, CGSize, + //UIView_UIViewGeometry, //UIView_UIViewHierarchy, //UIView, //UIViewController, @@ -161,8 +156,7 @@ pub trait Application: Sized { */ } //proxy.send_event(WidgetEvent {widget_id: 0} ); - let mut cached_hash: u64 = 0; - let mut widget_tree : Option = None; + let mut widget_tree: Option = None; event_loop.run( move |event: winit::event::Event, _, control_flow| { @@ -172,25 +166,43 @@ pub trait Application: Sized { match event { event::Event::MainEventsCleared => {} event::Event::UserEvent(widget_event) => { - let mut element = app.view(); - if let Some(ref widget_tree) = widget_tree { - element - .widget - .on_widget_event(widget_event, &mut messages, &widget_tree); + { + let mut element = app.view(); + if let Some(ref widget_tree) = widget_tree { + element.widget.on_widget_event( + widget_event, + &mut messages, + &widget_tree, + ); + } + debug!("Root widget before: {:#?}", widget_tree); } + let mut element = app.view(); - let hash = { - let mut hash = &mut crate::Hasher::default(); - element.widget.hash_layout(&mut hash); - hash.finish() - }; - if hash != cached_hash { - cached_hash = hash; - widget_tree = Some(element.update_or_add(Some(root_view), widget_tree.take())); + match element + .get_render_action(widget_tree.take().as_ref()) + { + RenderAction::Add | RenderAction::Update => { + debug!("Adding or updating root widget {:?} with {:?}", widget_tree.as_ref(), element.get_widget_type()); + widget_tree = Some(element.update_or_add( + Some(root_view), + widget_tree.take(), + )); + } + RenderAction::Remove => { + if let Some(node) = &widget_tree { + debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); + node.drop_from_ui(); + } + widget_tree = Some(element.update_or_add( + Some(root_view), + None, + )); + }, } + debug!("Root widget after: {:#?}", widget_tree); } - event::Event::RedrawRequested(_) => { - } + event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { event: _window_event, .. @@ -199,16 +211,10 @@ pub trait Application: Sized { let root_view: UIView = UIView(window.ui_view() as id); let mut element = app.view(); - let hash = { - let mut hash = &mut crate::Hasher::default(); - element.widget.hash_layout(&mut hash); - hash.finish() - }; - //widget_tree = Some(element.update_or_add(root_view, widget_tree)); - if hash != cached_hash { - cached_hash = hash; - widget_tree = Some(element.update_or_add(Some(root_view), widget_tree.take())); - } + widget_tree = Some(element.update_or_add( + Some(root_view), + widget_tree.take(), + )); } _ => { *control_flow = ControlFlow::Wait; diff --git a/ios/src/event.rs b/ios/src/event.rs index 42a9ede431..d09013fe19 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -43,7 +43,6 @@ impl EventHandler { if let Some(mut counter) = COUNTER { counter += 1; COUNTER = Some(counter); - debug!("WHAT THE FUCK IS THE COUNTER: {:?}!", counter); widget_id = counter; //widget_id = counter.fetch_add(0, Ordering::SeqCst); (*obj).set_ivar::("widget_id", widget_id); diff --git a/ios/src/layout.rs b/ios/src/layout.rs deleted file mode 100644 index c9eea939df..0000000000 --- a/ios/src/layout.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Position your widgets properly. -//mod debugger; -mod limits; -mod node; - -pub mod flex; - -//pub use debugger::Debugger; -pub use limits::Limits; -pub use node::Node; - -use crate::{Point, Rectangle, Vector}; - -/// The bounds of a [`Node`] and its children, using absolute coordinates. -/// -/// [`Node`]: struct.Node.html -#[derive(Debug, Clone, Copy)] -pub struct Layout<'a> { - position: Point, - node: &'a Node, -} - -impl<'a> Layout<'a> { - pub(crate) fn new(node: &'a Node) -> Self { - Self::with_offset(Vector::new(0.0, 0.0), node) - } - - pub(crate) fn with_offset(offset: Vector, node: &'a Node) -> Self { - let bounds = node.bounds(); - - Self { - position: Point::new(bounds.x, bounds.y) + offset, - node, - } - } - - /// Gets the bounds of the [`Layout`]. - /// - /// The returned [`Rectangle`] describes the position and size of a - /// [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Rectangle`]: struct.Rectangle.html - /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { - let bounds = self.node.bounds(); - - Rectangle { - x: self.position.x, - y: self.position.y, - width: bounds.width, - height: bounds.height, - } - } - - /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. - /// - /// [`Layout`]: struct.Layout.html - /// [`Node`]: struct.Node.html - pub fn children(&'a self) -> impl Iterator> { - self.node.children().iter().map(move |node| { - Layout::with_offset( - Vector::new(self.position.x, self.position.y), - node, - ) - }) - } -} diff --git a/ios/src/layout/DRUID_LICENSE b/ios/src/layout/DRUID_LICENSE deleted file mode 100644 index d645695673..0000000000 --- a/ios/src/layout/DRUID_LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/ios/src/layout/debugger.rs b/ios/src/layout/debugger.rs deleted file mode 100644 index e4b21609cc..0000000000 --- a/ios/src/layout/debugger.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{Color, Layout, Point, Renderer, Widget}; - -/// A renderer able to graphically explain a [`Layout`]. -/// -/// [`Layout`]: struct.Layout.html -pub trait Debugger: Renderer { - /// Explains the [`Layout`] of an [`Element`] for debugging purposes. - /// - /// This will be called when [`Element::explain`] has been used. It should - /// _explain_ the given [`Layout`] graphically. - /// - /// A common approach consists in recursively rendering the bounds of the - /// [`Layout`] and its children. - /// - /// [`Layout`]: struct.Layout.html - /// [`Element`]: ../struct.Element.html - /// [`Element::explain`]: ../struct.Element.html#method.explain - fn explain( - &mut self, - defaults: &Self::Defaults, - widget: &dyn Widget, - layout: Layout<'_>, - cursor_position: Point, - color: Color, - ) -> Self::Output; -} diff --git a/ios/src/layout/flex.rs b/ios/src/layout/flex.rs deleted file mode 100644 index 77ad20f49e..0000000000 --- a/ios/src/layout/flex.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Distribute elements using a flex-based layout. -// This code is heavily inspired by the [`druid`] codebase. -// -// [`druid`]: https://github.com/xi-editor/druid -// -// Copyright 2018 The xi-editor Authors, Héctor Ramón -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use crate::{ - layout::{Limits, Node}, - Align, Element, Point, Size, - widget::Widget, -}; - -/// The main axis of a flex layout. -#[derive(Debug)] -pub enum Axis { - /// The horizontal axis - Horizontal, - - /// The vertical axis - Vertical, -} - -impl Axis { - fn main(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.width, - Axis::Vertical => size.height, - } - } - - fn cross(&self, size: Size) -> f32 { - match self { - Axis::Horizontal => size.height, - Axis::Vertical => size.width, - } - } - - fn pack(&self, main: f32, cross: f32) -> (f32, f32) { - match self { - Axis::Horizontal => (main, cross), - Axis::Vertical => (cross, main), - } - } -} - -/// Computes the flex layout with the given axis and limits, applying spacing, -/// padding and alignment to the items as needed. -/// -/// It returns a new layout [`Node`]. -/// -/// [`Node`]: ../struct.Node.html -pub fn resolve( - axis: Axis, - limits: &Limits, - padding: f32, - spacing: f32, - align_items: Align, - items: &[Element<'_, Message>], -) -> Node -where - Message: 'static, -{ - let limits = limits.pad(padding); - let total_spacing = spacing * items.len().saturating_sub(1) as f32; - let max_cross = axis.cross(limits.max()); - - let mut fill_sum = 0; - let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill())); - let mut available = axis.main(limits.max()) - total_spacing; - - let mut nodes: Vec = Vec::with_capacity(items.len()); - nodes.resize(items.len(), Node::default()); - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.width(), - Axis::Vertical => child.height(), - } - .fill_factor(); - - if fill_factor == 0 { - let (max_width, max_height) = axis.pack(available, max_cross); - - let child_limits = - Limits::new(Size::ZERO, Size::new(max_width, max_height)); - - let layout = child.layout(&child_limits); - let size = layout.size(); - - available -= axis.main(size); - cross = cross.max(axis.cross(size)); - - nodes[i] = layout; - } else { - fill_sum += fill_factor; - } - } - - let remaining = available.max(0.0); - - for (i, child) in items.iter().enumerate() { - let fill_factor = match axis { - Axis::Horizontal => child.width(), - Axis::Vertical => child.height(), - } - .fill_factor(); - - if fill_factor != 0 { - let max_main = remaining * fill_factor as f32 / fill_sum as f32; - let min_main = if max_main.is_infinite() { - 0.0 - } else { - max_main - }; - - let (min_main, min_cross) = - axis.pack(min_main, axis.cross(limits.min())); - - let (max_main, max_cross) = - axis.pack(max_main, axis.cross(limits.max())); - - let child_limits = Limits::new( - Size::new(min_main, min_cross), - Size::new(max_main, max_cross), - ); - - let layout = child.layout(&child_limits); - cross = cross.max(axis.cross(layout.size())); - - nodes[i] = layout; - } - } - - let mut main = padding; - - for (i, node) in nodes.iter_mut().enumerate() { - if i > 0 { - main += spacing; - } - - let (x, y) = axis.pack(main, padding); - - node.move_to(Point::new(x, y)); - - match axis { - Axis::Horizontal => { - node.align(Align::Start, align_items, Size::new(0.0, cross)); - } - Axis::Vertical => { - node.align(align_items, Align::Start, Size::new(cross, 0.0)); - } - } - - let size = node.size(); - - main += axis.main(size); - } - - let (width, height) = axis.pack(main - padding, cross); - let size = limits.resolve(Size::new(width, height)); - - Node::with_children(size.pad(padding), nodes) -} diff --git a/ios/src/layout/limits.rs b/ios/src/layout/limits.rs deleted file mode 100644 index 664c881a4b..0000000000 --- a/ios/src/layout/limits.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{Length, Size}; - -/// A set of size constraints for layouting. -#[derive(Debug, Clone, Copy)] -pub struct Limits { - min: Size, - max: Size, - fill: Size, -} - -impl Limits { - /// No limits - pub const NONE: Limits = Limits { - min: Size::ZERO, - max: Size::INFINITY, - fill: Size::INFINITY, - }; - - /// Creates new [`Limits`] with the given minimum and maximum [`Size`]. - /// - /// [`Limits`]: struct.Limits.html - /// [`Size`]: ../struct.Size.html - pub const fn new(min: Size, max: Size) -> Limits { - Limits { - min, - max, - fill: Size::INFINITY, - } - } - - /// Returns the minimum [`Size`] of the [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - /// [`Size`]: ../struct.Size.html - pub fn min(&self) -> Size { - self.min - } - - /// Returns the maximum [`Size`] of the [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - /// [`Size`]: ../struct.Size.html - pub fn max(&self) -> Size { - self.max - } - - /// Returns the fill [`Size`] of the [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - /// [`Size`]: ../struct.Size.html - pub fn fill(&self) -> Size { - self.fill - } - - /// Applies a width constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn width(mut self, width: Length) -> Limits { - match width { - Length::Shrink => { - self.fill.width = self.min.width; - } - Length::Fill | Length::FillPortion(_) => { - self.fill.width = self.fill.width.min(self.max.width); - } - Length::Units(units) => { - let new_width = - (units as f32).min(self.max.width).max(self.min.width); - - self.min.width = new_width; - self.max.width = new_width; - self.fill.width = new_width; - } - } - - self - } - - /// Applies a height constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn height(mut self, height: Length) -> Limits { - match height { - Length::Shrink => { - self.fill.height = self.min.height; - } - Length::Fill | Length::FillPortion(_) => { - self.fill.height = self.fill.height.min(self.max.height); - } - Length::Units(units) => { - let new_height = - (units as f32).min(self.max.height).max(self.min.height); - - self.min.height = new_height; - self.max.height = new_height; - self.fill.height = new_height; - } - } - - self - } - - /// Applies a minimum width constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn min_width(mut self, min_width: u32) -> Limits { - self.min.width = - self.min.width.max(min_width as f32).min(self.max.width); - - self - } - - /// Applies a maximum width constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn max_width(mut self, max_width: u32) -> Limits { - self.max.width = - self.max.width.min(max_width as f32).max(self.min.width); - - self - } - - /// Applies a minimum height constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn min_height(mut self, min_height: u32) -> Limits { - self.min.height = - self.min.height.max(min_height as f32).min(self.max.height); - - self - } - - /// Applies a maximum height constraint to the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn max_height(mut self, max_height: u32) -> Limits { - self.max.height = - self.max.height.min(max_height as f32).max(self.min.height); - - self - } - - /// Shrinks the current [`Limits`] to account for the given padding. - /// - /// [`Limits`]: struct.Limits.html - pub fn pad(&self, padding: f32) -> Limits { - self.shrink(Size::new(padding * 2.0, padding * 2.0)) - } - - /// Shrinks the current [`Limits`] by the given [`Size`]. - /// - /// [`Limits`]: struct.Limits.html - /// [`Size`]: ../struct.Size.html - pub fn shrink(&self, size: Size) -> Limits { - let min = Size::new( - (self.min().width - size.width).max(0.0), - (self.min().height - size.height).max(0.0), - ); - - let max = Size::new( - (self.max().width - size.width).max(0.0), - (self.max().height - size.height).max(0.0), - ); - - let fill = Size::new( - (self.fill.width - size.width).max(0.0), - (self.fill.height - size.height).max(0.0), - ); - - Limits { min, max, fill } - } - - /// Removes the minimum width constraint for the current [`Limits`]. - /// - /// [`Limits`]: struct.Limits.html - pub fn loose(&self) -> Limits { - Limits { - min: Size::ZERO, - max: self.max, - fill: self.fill, - } - } - - /// Computes the resulting [`Size`] that fits the [`Limits`] given the - /// intrinsic size of some content. - /// - /// [`Limits`]: struct.Limits.html - pub fn resolve(&self, intrinsic_size: Size) -> Size { - Size::new( - intrinsic_size - .width - .min(self.max.width) - .max(self.fill.width), - intrinsic_size - .height - .min(self.max.height) - .max(self.fill.height), - ) - } -} diff --git a/ios/src/layout/node.rs b/ios/src/layout/node.rs deleted file mode 100644 index a265c46a06..0000000000 --- a/ios/src/layout/node.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::{Align, Point, Rectangle, Size}; - -/// The bounds of an element and its children. -#[derive(Debug, Clone, Default)] -pub struct Node { - bounds: Rectangle, - children: Vec, -} - -impl Node { - /// Creates a new [`Node`] with the given [`Size`]. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub const fn new(size: Size) -> Self { - Self::with_children(size, Vec::new()) - } - - /// Creates a new [`Node`] with the given [`Size`] and children. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub const fn with_children(size: Size, children: Vec) -> Self { - Node { - bounds: Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - children, - } - } - - /// Returns the [`Size`] of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - /// [`Size`]: ../struct.Size.html - pub fn size(&self) -> Size { - Size::new(self.bounds.width, self.bounds.height) - } - - /// Returns the bounds of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { - self.bounds - } - - /// Returns the children of the [`Node`]. - /// - /// [`Node`]: struct.Node.html - pub fn children(&self) -> &[Node] { - &self.children - } - - /// Aligns the [`Node`] in the given space. - /// - /// [`Node`]: struct.Node.html - pub fn align( - &mut self, - horizontal_alignment: Align, - vertical_alignment: Align, - space: Size, - ) { - match horizontal_alignment { - Align::Start => {} - Align::Center => { - self.bounds.x += (space.width - self.bounds.width) / 2.0; - } - Align::End => { - self.bounds.x += space.width - self.bounds.width; - } - } - - match vertical_alignment { - Align::Start => {} - Align::Center => { - self.bounds.y += (space.height - self.bounds.height) / 2.0; - } - Align::End => { - self.bounds.y += space.height - self.bounds.height; - } - } - } - - /// Moves the [`Node`] to the given position. - /// - /// [`Node`]: struct.Node.html - pub fn move_to(&mut self, position: Point) { - self.bounds.x = position.x; - self.bounds.y = position.y; - } -} diff --git a/ios/src/lib.rs b/ios/src/lib.rs index ff703cc3b2..d9ea55549b 100644 --- a/ios/src/lib.rs +++ b/ios/src/lib.rs @@ -13,8 +13,6 @@ pub use widget::{ Element, Widget, Text, TextInput, Column, }; use event::WidgetEvent; -mod layout; -pub use layout::Layout; pub use application::Application; pub type Hasher = std::collections::hash_map::DefaultHasher; diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 57db4e8ca2..c139cc171b 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,7 +1,4 @@ use crate::{ - layout::{self, - Layout, - }, Length, event::WidgetEvent, Hasher, @@ -52,7 +49,7 @@ pub use row::Row; pub use space::Space; */ -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum WidgetType { BaseElement, Button, @@ -72,8 +69,9 @@ pub enum WidgetType { use std::rc::Rc; use std::cell::RefCell; +#[derive(Debug, Clone)] pub struct WidgetNode { - pub (crate) view_id: id, + pub (crate) view_id: Option, //pub (crate) widget_id: u64, // Used for memory collection. @@ -86,7 +84,7 @@ pub struct WidgetNode { impl Default for WidgetNode { fn default() -> Self { Self { - view_id: 0 as id, + view_id: None, //widget_id: 0, related_ids: Vec::new(), widget_type: WidgetType::BaseElement, @@ -95,9 +93,9 @@ impl Default for WidgetNode { } } +/* impl Drop for WidgetNode { fn drop(&mut self) { - /* debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); use uikit_sys::{ UIView_UIViewHierarchy, @@ -117,12 +115,11 @@ impl Drop for WidgetNode { for i in &self.children { drop(i); } - */ } } - +*/ impl WidgetNode { - pub fn new(view_id: id, widget_type: WidgetType) -> Self { + pub fn new(view_id: Option, widget_type: WidgetType) -> Self { Self { view_id, related_ids: Vec::new(), @@ -130,6 +127,31 @@ impl WidgetNode { children: Vec::new(), } } + + pub fn drop_from_ui(&self) { + debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); + use uikit_sys::{ + UIView_UIViewHierarchy, + NSObject, INSObject, + }; + if let Some(view_id) = self.view_id { + let view = UIView(view_id); + unsafe { + view.removeFromSuperview(); + view.dealloc(); + } + } + for i in &self.related_ids { + let obj = NSObject(*i); + unsafe { + obj.dealloc(); + } + } + for i in &self.children { + i.borrow().drop_from_ui(); + } + } + pub fn add_related_id(&mut self, related_id: id) { self.related_ids.push(related_id); } @@ -146,7 +168,15 @@ impl WidgetNode { } } -pub trait Widget { +#[derive(Debug)] +pub enum RenderAction { + Add, + Remove, + Update, +} + +pub trait Widget //: Into + Sized +{ fn update_or_add( &mut self, _parent: Option, @@ -170,10 +200,18 @@ pub trait Widget { ) { debug!("on_widget_event for {:?}", self.get_widget_type()); } - fn layout( - &self, - limits: &layout::Limits, - ) -> layout::Node; + + fn get_render_action(&self, widget_node: Option<&WidgetNode>) -> RenderAction { + let action = if widget_node.is_none() { + RenderAction::Add + } else if widget_node.is_some() && widget_node.unwrap().widget_type == self.get_widget_type() { + RenderAction::Update + } else { + RenderAction::Remove + }; + debug!("RENDER ACTION FOR WIDGET {:?} is {:?}", self.get_widget_type(), action); + action + } fn width(&self) -> Length; @@ -184,6 +222,7 @@ pub trait Widget { pub struct Element<'a, Message> { pub(crate) widget: Box + 'a>, } + impl<'a, Message> Element<'a, Message> { /// Create a new [`Element`] containing the given [`Widget`]. /// @@ -195,6 +234,7 @@ impl<'a, Message> Element<'a, Message> { } } } + impl<'a, Message> Widget for Element<'a, Message> { fn hash_layout(&self, state: &mut Hasher) { @@ -204,19 +244,12 @@ impl<'a, Message> Widget for Element<'a, Message> self.widget.hash_layout(state); } - fn layout( - &self, - _limits: &layout::Limits, - ) -> layout::Node { - todo!(); - } - fn width(&self) -> Length { - todo!(); + self.widget.width() } fn height(&self) -> Length { - todo!(); + self.widget.height() } fn update_or_add(&mut self, parent: Option, old_node: Option) -> WidgetNode { @@ -226,6 +259,9 @@ impl<'a, Message> Widget for Element<'a, Message> fn get_widget_type(&self) -> WidgetType { self.widget.get_widget_type() } + fn get_render_action(&self, widget_node: Option<&WidgetNode>) -> RenderAction { + self.widget.get_render_action(widget_node) + } fn on_widget_event( &mut self, @@ -240,3 +276,21 @@ impl<'a, Message> Widget for Element<'a, Message> self.widget.on_widget_event(event, messages, widget_node); } } + +/* +impl<'a, Message> From + 'a>> for WidgetNode { + fn from(element: Box + 'a>) -> WidgetNode { + Widget::from(element) + } +} +impl<'a, Message> From> for WidgetNode { + fn from(element: Element<'a, Message>) -> WidgetNode { + Widget::from(element.widget) + } +} +impl<'a, Message> From<&Element<'a, Message>> for WidgetNode { + fn from(element: &Element<'a, Message>) -> WidgetNode { + Widget::from(element.widget) + } +} +*/ diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 29afc0bae0..be40511760 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -1,7 +1,6 @@ use crate::{ event::WidgetEvent, - layout, - widget::{WidgetNode, WidgetType}, + widget::{WidgetNode, WidgetType, RenderAction,}, Align, Element, Hasher, Length, Widget, }; use std::hash::Hash; @@ -148,6 +147,7 @@ where i.on_widget_event(event.clone(), messages, &node.borrow()); } } + fn hash_layout(&self, state: &mut Hasher) { struct Marker; std::any::TypeId::of::().hash(state); @@ -163,22 +163,6 @@ where child.widget.hash_layout(state); } } - fn layout(&self, limits: &layout::Limits) -> layout::Node { - let limits = limits - .max_width(self.max_width) - .max_height(self.max_height) - .width(self.width) - .height(self.height); - - layout::flex::resolve( - layout::flex::Axis::Vertical, - &limits, - self.padding as f32, - self.spacing as f32, - self.align_items, - &self.children, - ) - } fn width(&self) -> Length { self.width } @@ -192,55 +176,97 @@ where } fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { - let stack_view = unsafe { - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 400.0, - width: 300.0, - }, - }; - let stack_view = - UIStackView(UIStackView::alloc().initWithFrame_(rect)); - //let stack_view = UIStackView(UIStackView::alloc().init()); - //stack_view.setFrame_(rect); - stack_view.setAxis_( - UILayoutConstraintAxis_UILayoutConstraintAxisVertical, - ); - //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); - stack_view.setDistribution_( - UIStackViewDistribution_UIStackViewDistributionFill, - ); - if let Some(parent) = parent { - parent.addSubview_(stack_view.0); - } + match self.get_render_action(old_node.as_ref()) { + RenderAction::Add => { - /* - let view3 = UITextView(UITextView::alloc().init()); - let layout = NSLayoutDimension(view3.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - let layout = NSLayoutDimension(view3.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(120.0)).setActive_(true); - stack_view.addArrangedSubview_(view3.0); - */ + let stack_view = unsafe { + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 400.0, + width: 300.0, + }, + }; + let stack_view = + UIStackView(UIStackView::alloc().initWithFrame_(rect)); + //let stack_view = UIStackView(UIStackView::alloc().init()); + //stack_view.setFrame_(rect); + stack_view.setAxis_( + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + ); + //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setDistribution_( + UIStackViewDistribution_UIStackViewDistributionFill, + ); + if let Some(parent) = parent { + parent.addSubview_(stack_view.0); + } + /* + let view3 = UITextView(UITextView::alloc().init()); + let layout = NSLayoutDimension(view3.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); + let layout = NSLayoutDimension(view3.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(120.0)).setActive_(true); + stack_view.addArrangedSubview_(view3.0); + */ - stack_view - }; - let mut stackview_node = - WidgetNode::new(stack_view.0, WidgetType::Column); - for i in &mut self.children { - let node = i.update_or_add(None, None); - let subview = UIView(node.view_id); - stackview_node.add_child(node); - unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); + stack_view + }; + let mut stackview_node = + WidgetNode::new(Some(stack_view.0), self.get_widget_type()); + for (i, val) in self.children.iter_mut().enumerate() { + let node = val.update_or_add(None, None); + let subview = UIView(node.view_id.unwrap()); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + } + stackview_node + }, + RenderAction::Update => { + /* + if let Some(node) = old_node { + let stack_view = UIStackView(node.view_id); + let mut stackview_node = + WidgetNode::new(stack_view.0, WidgetType::Column); + for (i, val) in self.children.iter_mut().enumerate() { + if let Some(child) = node.children.get(i).as_deref() { + let node = val.update_or_add(None, Some((*child.borrow()).clone())); + } else { + let node = val.update_or_add(None, None); + let subview = UIView(node.view_id); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); + } + } + } + stackview_node + } else { + WidgetNode::default() + } + */ + WidgetNode::new(None, self.get_widget_type()) + }, + RenderAction::Remove => { + if let Some(node) = old_node { + node.drop_from_ui(); + } + WidgetNode::new(None, self.get_widget_type()) } } /* @@ -311,7 +337,7 @@ where stack_view.addArrangedSubview_(view2.0); } */ - stackview_node + //stackview_node } } @@ -323,3 +349,18 @@ where Element::new(column) } } +/* +impl<'a, Message> From> for WidgetNode { + fn from(column: Column<'a, Message>) -> WidgetNode { + let mut widget = WidgetNode::new(None, WidgetType::Column); + for i in &column.children { + widget.add_child(WidgetNode::from(i)); + } + widget + } +} +*/ +#[test] +fn test_foo() { + assert!(true); +} diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 03421c8f8c..4c8e4265f2 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -8,8 +8,8 @@ use crate::{ Length, VerticalAlignment, Widget, - widget::{WidgetNode, WidgetType}, - Hasher, layout, Size, + widget::{WidgetNode, WidgetType, RenderAction}, + Hasher, Size, }; use std::convert::TryInto; use std::ffi::CString; @@ -18,6 +18,7 @@ use uikit_sys::{ NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, }; +use std::marker::PhantomData; /// A paragraph of text. /// @@ -29,7 +30,7 @@ use uikit_sys::{ /// Text::new("I <3 iced!") /// .size(40); /// ``` -pub struct Text { +pub struct Text { content: String, size: Option, color: Option, @@ -38,9 +39,10 @@ pub struct Text { height: Length, horizontal_alignment: HorizontalAlignment, vertical_alignment: VerticalAlignment, + phantom: PhantomData, } -impl Text { +impl Text { /// Create a new fragment of [`Text`] with the given contents. /// /// [`Text`]: struct.Text.html @@ -54,6 +56,7 @@ impl Text { height: Length::Shrink, horizontal_alignment: HorizontalAlignment::Left, vertical_alignment: VerticalAlignment::Top, + phantom: PhantomData, } } @@ -121,7 +124,7 @@ impl Text { } } -impl<'a, Message> Widget for Text { +impl Widget for Text { fn hash_layout(&self, state: &mut Hasher) { struct Marker; @@ -137,52 +140,107 @@ impl<'a, Message> Widget for Text { } fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { - if let Some(_old_node) = old_node { - unimplemented!("Update this text with new text maybe"); - //WidgetNode::default() - } else { - - let label = unsafe { - let label = UILabel::alloc(); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.init(); - label.setText_(text.0); - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 0.0, - width: 0.0, - }, - }; - label.setFrame_(rect); - */ - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(10.0); - if let Some(color) = self.color { - let background = - UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - )); - label.setTextColor_(background.0) + /* + match element + .get_render_action(widget_tree.take().as_ref()) + { + RenderAction::Add | RenderAction::Update => { + debug!("Adding or updating root widget {:?} with {:?}", widget_tree.as_ref(), element.get_widget_type()); + widget_tree = Some(element.update_or_add( + Some(root_view), + widget_tree.take(), + )); } - if let Some(parent) = parent { - parent.addSubview_(label.0); + RenderAction::Remove => { + if let Some(node) = &widget_tree { + debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); + node.drop_from_ui(); + } + widget_tree = Some(element.update_or_add( + Some(root_view), + None, + )); + }, + } + */ + match self.get_render_action(old_node.as_ref()) { + + RenderAction::Add => { + + let label = unsafe { + let label = UILabel::alloc(); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.init(); + label.setText_(text.0); + /* + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 0.0, + width: 0.0, + }, + }; + label.setFrame_(rect); + */ + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(10.0); + if let Some(color) = self.color { + let background = + UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + )); + label.setTextColor_(background.0) + } + if let Some(parent) = parent { + parent.addSubview_(label.0); + } + label + }; + WidgetNode::new(Some(label.0), self.get_widget_type()) + }, + RenderAction::Update => { + if let Some(node) = old_node { + let label = unsafe { + let label = UILabel(node.view_id.unwrap()); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.setText_(text.0); + label + }; + WidgetNode::new(Some(label.0), self.get_widget_type()) + } else { + WidgetNode::new(None, self.get_widget_type()) + } + } + RenderAction::Remove => { + if let Some(node) = old_node { + node.drop_from_ui(); + /* + let view = UIView(node.view_id); + unsafe { + view.removeFromSuperview(); + }*/ } - label - }; - WidgetNode::new(label.0, WidgetType::Text) + WidgetNode::new(None, self.get_widget_type()) + }, } } @@ -194,31 +252,19 @@ impl<'a, Message> Widget for Text { self.height } - fn layout( - &self, - _limits: &layout::Limits, - ) -> layout::Node { - todo!() - /* - let limits = limits.width(self.width).height(self.height); - - let size = self.size.unwrap_or(0); - - let bounds = limits.max(); - - let (width, height) = - renderer.measure(&self.content, size, self.font, bounds); - - let size = limits.resolve(Size::new(width, height)); +} - layout::Node::new(size) - */ +impl<'a, Message> From> for Element<'a, Message> +where Message: 'a +{ + fn from(text: Text) -> Element<'a, Message> { + Element::new(text) } - } -impl<'a, Message> From for Element<'a, Message> { - fn from(text: Text) -> Element<'a, Message> { - Element::new(text) +impl From> for WidgetNode +{ + fn from(_text: Text) -> WidgetNode { + WidgetNode::new(None, WidgetType::Text) } } diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 16b10b342f..186a26d58e 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -6,7 +6,7 @@ //! [`State`]: struct.State.html use crate::{ event::{EventHandler, WidgetEvent}, - layout, Element, Hasher, Length, Widget, + Element, Hasher, Length, Widget, widget::{ WidgetType, WidgetNode, @@ -175,6 +175,7 @@ where } fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { + debug!("TEXT WIDGET ADD OR UPDATE old_node :{:?}", old_node); if let Some(old_node) = old_node { old_node } else { @@ -221,14 +222,8 @@ where ui_textview }; - use std::hash::Hasher; - let hash = { - let mut hash = &mut crate::Hasher::default(); - self.hash_layout(&mut hash); - hash.finish() - }; - WidgetNode::new(textview.0, WidgetType::TextInput) + WidgetNode::new(Some(textview.0), WidgetType::TextInput) } } @@ -246,7 +241,7 @@ where self.widget_id, widget_node.view_id, ); - if widget_event.id as id == widget_node.view_id { + if Some(widget_event.id as id) == widget_node.view_id { let ui_textview = UITextView(widget_event.id as id); let value = unsafe { let value = NSString(ui_textview.text()); @@ -290,16 +285,12 @@ where */ } - fn layout(&self, limits: &layout::Limits) -> layout::Node { - todo!(); - } - fn width(&self) -> Length { - todo!(); + self.width } fn height(&self) -> Length { - todo!(); + todo!() } } @@ -311,6 +302,11 @@ where Element::new(text_input) } } +impl<'a, Message> From> for WidgetNode { + fn from(_text_input: TextInput<'a, Message>) -> WidgetNode { + WidgetNode::new(None, WidgetType::TextInput) + } +} /// The state of a [`TextInput`]. /// From 96512cdffb059a61cceee842807a5894c549a77c Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sun, 19 Jul 2020 20:55:07 -0700 Subject: [PATCH 20/33] Cleaned up some of the widget tree node structures --- ios/examples/ios-example.rs | 64 ++++------ ios/src/application.rs | 78 ++++++------ ios/src/widget.rs | 146 +++++++++++++++-------- ios/src/widget/column.rs | 222 +++++++---------------------------- ios/src/widget/text.rs | 148 ++++++----------------- ios/src/widget/text_input.rs | 112 ++++++------------ 6 files changed, 274 insertions(+), 496 deletions(-) diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index 9aafddbd7c..dbc303600d 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -56,7 +56,7 @@ impl Sandbox for Simple { debug!("GOT NEW MESSAGE: {:?}", message); match message { Message::TextUpdated(val) => { - self.text = val; + self.text = format!("{}{}", self.text, val); }, _ => { }, @@ -64,48 +64,26 @@ impl Sandbox for Simple { } fn view(&mut self) -> Element { - debug!("RERUNNING VIEW : {:#?}", self); - if self.text.starts_with("A") { - debug!("RETURNING TEXT ONLY VIEW NOW"); - Text::new("THE TEXT STARTS WITH THE LETTER A").into() - } else { - let column = Column::new() - .push( - Column::new() - //.push( - // Text::new(String::from("FIRST FIRST")).color(Color::BLACK) - //) - .push( - TextInput::new( - &mut self.text_state, - "", - "", - |s| { - debug!("The 1st text box has \"{}\" in it!", s); - Message::TextUpdated(s) - } - ).on_submit(Message::TextSubmit), - ) + let column = Column::new() + .push( + //Text::new(String::from("SECOND SECOND")).color(Color::BLACK) + Text::new(format!("THIS IS THE TEXT: {:?}", self.text)).color(Color::BLACK) + ) + .push( + TextInput::new( + &mut self.text_state2, + "", + "", + |s| { + debug!("The 2nd text box has \"{}\" in it!", s); + Message::TextUpdated(s) + } ) - .push( - Text::new(String::from("SECOND SECOND")).color(Color::BLACK) - ) - .push( - TextInput::new( - &mut self.text_state2, - "", - "", - |s| { - debug!("The 2nd text box has \"{}\" in it!", s); - Message::TextUpdated(s) - } - ) - ) - //.push( - // Text::new(&self.text).color(Color::BLACK) - //) - ; - column.into() - } + ) + .push( + Text::new(&self.text).color(Color::BLACK) + ) + ; + column.into() } } diff --git a/ios/src/application.rs b/ios/src/application.rs index 10305acb2c..0d9dded7cf 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -155,8 +155,7 @@ pub trait Application: Sized { root_view.setFrame_(rect); */ } - //proxy.send_event(WidgetEvent {widget_id: 0} ); - let mut widget_tree: Option = None; + let mut widget_tree: WidgetNode = app.view().get_widget_node(); event_loop.run( move |event: winit::event::Event, _, control_flow| { @@ -168,39 +167,54 @@ pub trait Application: Sized { event::Event::UserEvent(widget_event) => { { let mut element = app.view(); - if let Some(ref widget_tree) = widget_tree { - element.widget.on_widget_event( - widget_event, - &mut messages, - &widget_tree, - ); - } - debug!("Root widget before: {:#?}", widget_tree); + element.widget.on_widget_event( + widget_event, + &mut messages, + &widget_tree, + ); + debug!("Root widget before: {:?}", widget_tree); + } + for message in messages { + let (command, subscription) = runtime.enter(|| { + let command = app.update(message); + let subscription = app.subscription(); + + (command, subscription) + }); + + runtime.spawn(command); + runtime.track(subscription); } let mut element = app.view(); + let new_tree = element.build_uiview(); + if new_tree != widget_tree { + new_tree.draw(root_view); + widget_tree.drop_from_ui(); + widget_tree = new_tree; + } + /* match element - .get_render_action(widget_tree.take().as_ref()) + .get_render_action(widget_tree) { RenderAction::Add | RenderAction::Update => { debug!("Adding or updating root widget {:?} with {:?}", widget_tree.as_ref(), element.get_widget_type()); - widget_tree = Some(element.update_or_add( + widget_tree = element.update_or_add( Some(root_view), - widget_tree.take(), - )); + widget_tree, + ); } RenderAction::Remove => { - if let Some(node) = &widget_tree { - debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); - node.drop_from_ui(); - } - widget_tree = Some(element.update_or_add( + debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); + node.drop_from_ui(); + widget_tree = element.update_or_add( Some(root_view), - None, - )); + widget_tree, + ); }, } - debug!("Root widget after: {:#?}", widget_tree); + */ + debug!("Root widget after: {:?}", widget_tree); } event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { @@ -208,29 +222,15 @@ pub trait Application: Sized { .. } => {} event::Event::NewEvents(event::StartCause::Init) => { - let root_view: UIView = UIView(window.ui_view() as id); let mut element = app.view(); - - widget_tree = Some(element.update_or_add( - Some(root_view), - widget_tree.take(), - )); + widget_tree = element.build_uiview(); + let root_view: UIView = UIView(window.ui_view() as id); + widget_tree.draw(root_view); } _ => { *control_flow = ControlFlow::Wait; } } - for message in messages { - let (command, subscription) = runtime.enter(|| { - let command = app.update(message); - let subscription = app.subscription(); - - (command, subscription) - }); - - runtime.spawn(command); - runtime.track(subscription); - } }, ); } diff --git a/ios/src/widget.rs b/ios/src/widget.rs index c139cc171b..1b7157fe61 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,12 +1,5 @@ -use crate::{ - Length, - event::WidgetEvent, - Hasher, -}; -use uikit_sys::{ - UIView, - id, -}; +use crate::{event::WidgetEvent, Hasher, Length}; +use uikit_sys::{id, UIView}; /* pub mod button; @@ -66,26 +59,35 @@ pub enum WidgetType { Row, Space, } -use std::rc::Rc; use std::cell::RefCell; +use std::rc::Rc; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct WidgetNode { - pub (crate) view_id: Option, + pub(crate) view_id: id, + pub(crate) hash: u64, //pub (crate) widget_id: u64, // Used for memory collection. related_ids: Vec, pub widget_type: WidgetType, // Used in things like Row, Column and Container. - pub (crate) children: Vec>>, + pub(crate) children: Vec>>, +} + +impl PartialEq for WidgetNode { + fn eq(&self, other: &Self) -> bool { + self.hash == other.hash + && self.widget_type == other.widget_type + && self.children == other.children + } } impl Default for WidgetNode { fn default() -> Self { Self { - view_id: None, - //widget_id: 0, + view_id: 0 as id, + hash: 0, related_ids: Vec::new(), widget_type: WidgetType::BaseElement, children: Vec::new(), @@ -119,9 +121,10 @@ impl Drop for WidgetNode { } */ impl WidgetNode { - pub fn new(view_id: Option, widget_type: WidgetType) -> Self { + pub fn new(view_id: id, widget_type: WidgetType, hash: u64) -> Self { Self { view_id, + hash, related_ids: Vec::new(), widget_type, children: Vec::new(), @@ -130,21 +133,18 @@ impl WidgetNode { pub fn drop_from_ui(&self) { debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); - use uikit_sys::{ - UIView_UIViewHierarchy, - NSObject, INSObject, - }; - if let Some(view_id) = self.view_id { - let view = UIView(view_id); + use uikit_sys::{INSObject, NSObject, UIView_UIViewHierarchy}; + if self.view_id != 0 as id { + let view = UIView(self.view_id); unsafe { view.removeFromSuperview(); - view.dealloc(); + //view.dealloc(); } } for i in &self.related_ids { let obj = NSObject(*i); unsafe { - obj.dealloc(); + //obj.dealloc(); } } for i in &self.children { @@ -152,6 +152,16 @@ impl WidgetNode { } } + + pub fn draw(&self, parent: UIView) { + use uikit_sys::UIView_UIViewHierarchy; + if self.view_id != 0 as id { + unsafe { + parent.addSubview_(self.view_id); + } + } + } + pub fn add_related_id(&mut self, related_id: id) { self.related_ids.push(related_id); } @@ -175,18 +185,29 @@ pub enum RenderAction { Update, } -pub trait Widget //: Into + Sized -{ - fn update_or_add( - &mut self, - _parent: Option, - _old_node: Option, - ) -> WidgetNode { - unimplemented!("USING BASE IMPLEMENTATION FOR UPDATE_OR_ADD"); +pub trait Widget { + fn get_widget_type(&self) -> WidgetType; + + fn get_widget_node(&self) -> WidgetNode { + let hash = self.get_my_hash(); + WidgetNode::new(0 as id, self.get_widget_type(), hash) } - fn get_widget_type(&self) -> WidgetType { - unimplemented!("USING BASE IMPLEMENTATION for GET_WIDGET_TYPE"); + + fn build_uiview(&self) -> WidgetNode { + unimplemented!( + "{:?} using base implementation", + self.get_widget_type() + ); } + + fn get_my_hash(&self) -> u64 { + use std::hash::Hasher; + let hasher = &mut crate::Hasher::default(); + self.hash_layout(hasher); + + hasher.finish() + } + fn hash_layout(&self, state: &mut Hasher); fn on_widget_event( &mut self, @@ -201,15 +222,24 @@ pub trait Widget //: Into + Sized debug!("on_widget_event for {:?}", self.get_widget_type()); } - fn get_render_action(&self, widget_node: Option<&WidgetNode>) -> RenderAction { + fn get_render_action( + &self, + widget_node: Option<&WidgetNode>, + ) -> RenderAction { let action = if widget_node.is_none() { RenderAction::Add - } else if widget_node.is_some() && widget_node.unwrap().widget_type == self.get_widget_type() { + } else if widget_node.is_some() + && widget_node.unwrap().widget_type == self.get_widget_type() + { RenderAction::Update } else { RenderAction::Remove }; - debug!("RENDER ACTION FOR WIDGET {:?} is {:?}", self.get_widget_type(), action); + debug!( + "RENDER ACTION FOR WIDGET {:?} is {:?}", + self.get_widget_type(), + action + ); action } @@ -218,10 +248,24 @@ pub trait Widget //: Into + Sized fn height(&self) -> Length; } +//pub type Element<'a, Message> = ElementTemplate + 'a>>; + #[allow(missing_debug_implementations)] pub struct Element<'a, Message> { - pub(crate) widget: Box + 'a>, + pub widget: Box + 'a>, +} + +/* +impl> Into for T { + fn into(self) -> WidgetNode { + let mut node = WidgetNode::new(None, self.get_widget_type()); + for i in &self.get_element_children() { + node.add_child(i.widget.into()); + } + node + } } +*/ impl<'a, Message> Element<'a, Message> { /// Create a new [`Element`] containing the given [`Widget`]. @@ -235,8 +279,7 @@ impl<'a, Message> Element<'a, Message> { } } -impl<'a, Message> Widget for Element<'a, Message> -{ +impl<'a, Message> Widget for Element<'a, Message> { fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash; struct Marker; @@ -252,17 +295,20 @@ impl<'a, Message> Widget for Element<'a, Message> self.widget.height() } - fn update_or_add(&mut self, parent: Option, old_node: Option) -> WidgetNode { - self.widget.update_or_add(parent, old_node) - } - fn get_widget_type(&self) -> WidgetType { self.widget.get_widget_type() } - fn get_render_action(&self, widget_node: Option<&WidgetNode>) -> RenderAction { + fn get_render_action( + &self, + widget_node: Option<&WidgetNode>, + ) -> RenderAction { self.widget.get_render_action(widget_node) } + fn build_uiview(&self) -> WidgetNode { + self.widget.build_uiview() + } + fn on_widget_event( &mut self, event: WidgetEvent, @@ -278,16 +324,16 @@ impl<'a, Message> Widget for Element<'a, Message> } /* -impl<'a, Message> From + 'a>> for WidgetNode { - fn from(element: Box + 'a>) -> WidgetNode { - Widget::from(element) - } -} impl<'a, Message> From> for WidgetNode { fn from(element: Element<'a, Message>) -> WidgetNode { Widget::from(element.widget) } } +impl<'a, Message> From + 'a>> for WidgetNode { + fn from(element: Box + 'a>) -> WidgetNode { + Widget::from(element) + } +} impl<'a, Message> From<&Element<'a, Message>> for WidgetNode { fn from(element: &Element<'a, Message>) -> WidgetNode { Widget::from(element.widget) diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index be40511760..2357cbe5f8 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -135,7 +135,6 @@ where fn on_widget_event( &mut self, event: WidgetEvent, - //_layout: Layout<'_>, messages: &mut Vec, widget_node: &WidgetNode, ) { @@ -143,7 +142,7 @@ where for (i, node) in &mut self.children.iter_mut().zip(widget_node.children.iter()) { - debug!("on_widget_event for {:?} child", i.get_widget_type()); + debug!("on_widget_event for {:?} child", i.get_widget_node()); i.on_widget_event(event.clone(), messages, &node.borrow()); } } @@ -174,170 +173,52 @@ where fn get_widget_type(&self) -> WidgetType { WidgetType::Column } - - fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { - match self.get_render_action(old_node.as_ref()) { - RenderAction::Add => { - - let stack_view = unsafe { - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 400.0, - width: 300.0, - }, - }; - let stack_view = - UIStackView(UIStackView::alloc().initWithFrame_(rect)); - //let stack_view = UIStackView(UIStackView::alloc().init()); - //stack_view.setFrame_(rect); - stack_view.setAxis_( - UILayoutConstraintAxis_UILayoutConstraintAxisVertical, - ); - //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); - stack_view.setDistribution_( - UIStackViewDistribution_UIStackViewDistributionFill, - ); - if let Some(parent) = parent { - parent.addSubview_(stack_view.0); - } - - /* - let view3 = UITextView(UITextView::alloc().init()); - let layout = NSLayoutDimension(view3.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - let layout = NSLayoutDimension(view3.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(120.0)).setActive_(true); - stack_view.addArrangedSubview_(view3.0); - */ - - stack_view - }; - let mut stackview_node = - WidgetNode::new(Some(stack_view.0), self.get_widget_type()); - for (i, val) in self.children.iter_mut().enumerate() { - let node = val.update_or_add(None, None); - let subview = UIView(node.view_id.unwrap()); - stackview_node.add_child(node); - unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); - } - } - stackview_node - }, - RenderAction::Update => { - /* - if let Some(node) = old_node { - let stack_view = UIStackView(node.view_id); - let mut stackview_node = - WidgetNode::new(stack_view.0, WidgetType::Column); - for (i, val) in self.children.iter_mut().enumerate() { - if let Some(child) = node.children.get(i).as_deref() { - let node = val.update_or_add(None, Some((*child.borrow()).clone())); - } else { - let node = val.update_or_add(None, None); - let subview = UIView(node.view_id); - stackview_node.add_child(node); - unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); - } - } - } - stackview_node - } else { - WidgetNode::default() - } - */ - WidgetNode::new(None, self.get_widget_type()) - }, - RenderAction::Remove => { - if let Some(node) = old_node { - node.drop_from_ui(); - } - WidgetNode::new(None, self.get_widget_type()) - } + fn get_widget_node(&self) -> WidgetNode { + let mut node = WidgetNode::new(0 as id, WidgetType::Column, self.get_my_hash()); + for i in &self.children { + node.add_child(i.get_widget_node()); } - /* - if let Some(old_node) = old_node { - if self.children.len() != old_node.children.len() { - for i in &mut self.children { - let node = i.update_or_add(None, None); - let subview = UIView(node.view_id); - stackview_node.add_child(node); - unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); - } - } - } else { - for (i, node) in - self.children.iter_mut().zip(old_node.children.iter()) - { - if i.get_widget_type() == node.borrow().widget_type { - } else { - unsafe { - let view = UIView(node.borrow().view_id); - //view.removeFromSuperview(); - stack_view.removeArrangedSubview_(node.borrow().view_id); - } - let node = i.update_or_add(None, Some(node.borrow())); - let subview = UIView(node.view_id); - stackview_node.add_child(node); - unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); - } - } - } + node + } + fn build_uiview(&self) -> WidgetNode { + let stack_view = unsafe { + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 400.0, + width: 300.0, + }, + }; + let stack_view = + UIStackView(UIStackView::alloc().initWithFrame_(rect)); + //let stack_view = UIStackView(UIStackView::alloc().init()); + //stack_view.setFrame_(rect); + stack_view.setAxis_( + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + ); + //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setDistribution_( + UIStackViewDistribution_UIStackViewDistributionFill, + ); + stack_view + }; + let mut stackview_node = + WidgetNode::new(stack_view.0, self.get_widget_type(), self.get_my_hash()); + for (i, val) in self.children.iter().enumerate() { + let node = val.build_uiview(); + let subview = UIView(node.view_id); + stackview_node.add_child(node); + unsafe { + let layout = NSLayoutDimension(subview.heightAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + let layout = NSLayoutDimension(subview.widthAnchor()); + NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + .setActive_(true); + stack_view.addArrangedSubview_(subview.0); } - } else { - */ - //} - /* - unsafe { - - stack_view.setSpacing_(10.0); - let view1 = UIView(UIView::alloc().init()); - view1.setBackgroundColor_(UIColor::redColor()); - let layout = NSLayoutDimension(view1.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - - let layout = NSLayoutDimension(view1.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - stack_view.addArrangedSubview_(view1.0); - - let view2 = UIView(UIView::alloc().init()); - view2.setBackgroundColor_(UIColor::blueColor()); - let layout = NSLayoutDimension(view2.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - let layout = NSLayoutDimension(view2.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)).setActive_(true); - stack_view.addArrangedSubview_(view2.0); } - */ - //stackview_node + stackview_node } } @@ -349,18 +230,3 @@ where Element::new(column) } } -/* -impl<'a, Message> From> for WidgetNode { - fn from(column: Column<'a, Message>) -> WidgetNode { - let mut widget = WidgetNode::new(None, WidgetType::Column); - for i in &column.children { - widget.add_child(WidgetNode::from(i)); - } - widget - } -} -*/ -#[test] -fn test_foo() { - assert!(true); -} diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 4c8e4265f2..43db2f1ce5 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -135,114 +135,49 @@ impl Widget for Text { self.width.hash(state); self.height.hash(state); } + fn get_widget_type(&self) -> WidgetType { WidgetType::Text } - - fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { - /* - match element - .get_render_action(widget_tree.take().as_ref()) - { - RenderAction::Add | RenderAction::Update => { - debug!("Adding or updating root widget {:?} with {:?}", widget_tree.as_ref(), element.get_widget_type()); - widget_tree = Some(element.update_or_add( - Some(root_view), - widget_tree.take(), - )); - } - RenderAction::Remove => { - if let Some(node) = &widget_tree { - debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); - node.drop_from_ui(); - } - widget_tree = Some(element.update_or_add( - Some(root_view), - None, + fn build_uiview(&self) -> WidgetNode { + let label = unsafe { + let label = UILabel::alloc(); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(self.content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + self.content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.init(); + label.setText_(text.0); + /* + let rect = CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + height: 0.0, + width: 0.0, + }, + }; + label.setFrame_(rect); + */ + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(10.0); + if let Some(color) = self.color { + let background = + UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), )); - }, - } - */ - match self.get_render_action(old_node.as_ref()) { - - RenderAction::Add => { - - let label = unsafe { - let label = UILabel::alloc(); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.init(); - label.setText_(text.0); - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 0.0, - width: 0.0, - }, - }; - label.setFrame_(rect); - */ - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(10.0); - if let Some(color) = self.color { - let background = - UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - )); - label.setTextColor_(background.0) - } - if let Some(parent) = parent { - parent.addSubview_(label.0); - } - label - }; - WidgetNode::new(Some(label.0), self.get_widget_type()) - }, - RenderAction::Update => { - if let Some(node) = old_node { - let label = unsafe { - let label = UILabel(node.view_id.unwrap()); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.setText_(text.0); - label - }; - WidgetNode::new(Some(label.0), self.get_widget_type()) - } else { - WidgetNode::new(None, self.get_widget_type()) - } + label.setTextColor_(background.0) } - RenderAction::Remove => { - if let Some(node) = old_node { - node.drop_from_ui(); - /* - let view = UIView(node.view_id); - unsafe { - view.removeFromSuperview(); - }*/ - } - WidgetNode::new(None, self.get_widget_type()) - }, - } - + label + }; + WidgetNode::new(label.0, self.get_widget_type(), self.get_my_hash()) } fn width(&self) -> Length { self.width @@ -261,10 +196,3 @@ where Message: 'a Element::new(text) } } - -impl From> for WidgetNode -{ - fn from(_text: Text) -> WidgetNode { - WidgetNode::new(None, WidgetType::Text) - } -} diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 186a26d58e..a6727cd6db 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -58,7 +58,6 @@ pub struct TextInput<'a, Message> { on_change: Rc Message>>, on_submit: Option, style_sheet: Box, - widget_id: u64, } impl<'a, Message> TextInput<'a, Message> { @@ -94,7 +93,6 @@ impl<'a, Message> TextInput<'a, Message> { on_change: Rc::new(Box::new(on_change)), on_submit: None, style_sheet: Default::default(), - widget_id: 0, } } @@ -170,64 +168,52 @@ where self.padding.hash(state); self.size.hash(state); } + fn get_widget_type(&self) -> WidgetType { WidgetType::TextInput } - fn update_or_add(&mut self, parent: Option, old_node: Option,) -> WidgetNode { - debug!("TEXT WIDGET ADD OR UPDATE old_node :{:?}", old_node); - if let Some(old_node) = old_node { - old_node - } else { - let textview = unsafe { - let ui_textview = { - if parent.is_none() { - UITextView(UITextView::alloc().init()) - } else { - let input_rect = CGRect { - origin: CGPoint { x: 10.0, y: 10.0 }, - size: CGSize { - width: 100.0, - height: 100.0, - }, - }; - UITextView(UITextView::alloc().initWithFrame_textContainer_( - input_rect, - 0 as id, - )) - } - }; - let on_change = EventHandler::new(ui_textview.0); - self.widget_id = on_change.widget_id; + fn build_uiview(&self) -> WidgetNode { + let textview = unsafe { + let ui_textview = { + // TODO: Use something better than just a rect. + UITextView(UITextView::alloc().init()) /* - input.addTarget_action_forControlEvents_( - on_change.id, - sel!(sendEvent), - uikit_sys::UIControlEvents_UIControlEventValueChanged, - ); - */ - // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc - let center = - NSNotificationCenter(NSNotificationCenter::defaultCenter()); - center.addObserver_selector_name_object_( - on_change.id, - sel!(sendEvent), - UITextViewTextDidChangeNotification, - ui_textview.0, - ); - //parent.addSubview_(ui_textview.0); - if let Some(parent) = parent { - parent.addSubview_(ui_textview.0); + if parent.is_none() { + UITextView(UITextView::alloc().init()) + } else { + let input_rect = CGRect { + origin: CGPoint { x: 10.0, y: 10.0 }, + size: CGSize { + width: 100.0, + height: 100.0, + }, + }; + UITextView(UITextView::alloc().initWithFrame_textContainer_( + input_rect, + 0 as id, + )) } - ui_textview + */ }; + let on_change = EventHandler::new(ui_textview.0); + // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc + let center = + NSNotificationCenter(NSNotificationCenter::defaultCenter()); + center.addObserver_selector_name_object_( + on_change.id, + sel!(sendEvent), + UITextViewTextDidChangeNotification, + ui_textview.0, + ); + ui_textview + }; - WidgetNode::new(Some(textview.0), WidgetType::TextInput) - } - + WidgetNode::new(textview.0, self.get_widget_type(), self.get_my_hash()) } + fn on_widget_event( &mut self, widget_event: WidgetEvent, @@ -238,10 +224,9 @@ where "on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, self.widget_id: {:?} widget_node.view_id {:?}", widget_event.id, widget_event.widget_id, - self.widget_id, widget_node.view_id, ); - if Some(widget_event.id as id) == widget_node.view_id { + if widget_event.id as id == widget_node.view_id { let ui_textview = UITextView(widget_event.id as id); let value = unsafe { let value = NSString(ui_textview.text()); @@ -260,29 +245,9 @@ where } } else { self.value = value; - - messages.push((self.on_change)(self.value.clone())); - } - } - - /* - debug!("on_widget_event TEXT UIVIEW {:?}", self.ui_textview.is_some()); - if let Some(ui_textview) = self.ui_textview { - if widget_event.widget_id == self.widget_id { - let value = unsafe { - let value = NSString(ui_textview.text()); - let len = value.lengthOfBytesUsingEncoding_( - uikit_sys::NSUTF8StringEncoding, - ); - let bytes = value.UTF8String() as *const u8; - String::from_utf8(std::slice::from_raw_parts(bytes, len.try_into().unwrap()).to_vec()).unwrap() - }; - self.value = value; - messages.push((self.on_change)(self.value.clone())); } } - */ } fn width(&self) -> Length { @@ -302,11 +267,6 @@ where Element::new(text_input) } } -impl<'a, Message> From> for WidgetNode { - fn from(_text_input: TextInput<'a, Message>) -> WidgetNode { - WidgetNode::new(None, WidgetType::TextInput) - } -} /// The state of a [`TextInput`]. /// From 7c1809c5f68a64271f47d579bab5613659d7533b Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Thu, 23 Jul 2020 21:50:23 -0700 Subject: [PATCH 21/33] Added a reasonable widget shadow representation --- ios/examples/ios-example.rs | 48 +++++----- ios/src/application.rs | 34 +------ ios/src/widget.rs | 171 +++++++++++++++++++++++++++++++---- ios/src/widget/column.rs | 107 ++++++++++++++++------ ios/src/widget/text.rs | 19 +++- ios/src/widget/text_input.rs | 14 ++- 6 files changed, 287 insertions(+), 106 deletions(-) diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index dbc303600d..8e79fac9e8 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -56,7 +56,7 @@ impl Sandbox for Simple { debug!("GOT NEW MESSAGE: {:?}", message); match message { Message::TextUpdated(val) => { - self.text = format!("{}{}", self.text, val); + self.text = val; }, _ => { }, @@ -64,26 +64,32 @@ impl Sandbox for Simple { } fn view(&mut self) -> Element { - let column = Column::new() - .push( - //Text::new(String::from("SECOND SECOND")).color(Color::BLACK) - Text::new(format!("THIS IS THE TEXT: {:?}", self.text)).color(Color::BLACK) - ) - .push( - TextInput::new( - &mut self.text_state2, - "", - "", - |s| { - debug!("The 2nd text box has \"{}\" in it!", s); - Message::TextUpdated(s) - } + if self.text.starts_with("B") { + TextInput::new( + &mut self.text_state2, + "", + "", + |s| { + Message::TextUpdated(s) + } + ).into() + } else { + let column = Column::new() + .push( + TextInput::new( + &mut self.text_state, + "", + "", + |s| { + Message::TextUpdated(s) + } + ) ) - ) - .push( - Text::new(&self.text).color(Color::BLACK) - ) - ; - column.into() + .push( + Text::new(format!("TEXT2: {}", &self.text)).color(Color::BLACK) + ) + ; + column.into() + } } } diff --git a/ios/src/application.rs b/ios/src/application.rs index 0d9dded7cf..6df654479c 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -186,35 +186,9 @@ pub trait Application: Sized { runtime.track(subscription); } let mut element = app.view(); - let new_tree = element.build_uiview(); - if new_tree != widget_tree { - new_tree.draw(root_view); - widget_tree.drop_from_ui(); - widget_tree = new_tree; - } - /* - - match element - .get_render_action(widget_tree) - { - RenderAction::Add | RenderAction::Update => { - debug!("Adding or updating root widget {:?} with {:?}", widget_tree.as_ref(), element.get_widget_type()); - widget_tree = element.update_or_add( - Some(root_view), - widget_tree, - ); - } - RenderAction::Remove => { - debug!("Removing root widget {:?} with {:?}", node, element.get_widget_type()); - node.drop_from_ui(); - widget_tree = element.update_or_add( - Some(root_view), - widget_tree, - ); - }, - } - */ - debug!("Root widget after: {:?}", widget_tree); + let new_tree = element.build_uiview(true); + widget_tree.merge(&new_tree, Some(root_view)); + debug!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { @@ -223,7 +197,7 @@ pub trait Application: Sized { } => {} event::Event::NewEvents(event::StartCause::Init) => { let mut element = app.view(); - widget_tree = element.build_uiview(); + widget_tree = element.build_uiview(true); let root_view: UIView = UIView(window.ui_view() as id); widget_tree.draw(root_view); } diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 1b7157fe61..e3ee9c9279 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,4 +1,6 @@ use crate::{event::WidgetEvent, Hasher, Length}; +use std::cell::RefCell; +use std::rc::Rc; use uikit_sys::{id, UIView}; /* @@ -48,10 +50,10 @@ pub enum WidgetType { Button, Scrollable, Slider, - Text, + Text(String), TextInput, Checkbox, - Column, + Column(Vec>>), Container, Image, ProgressBar, @@ -59,10 +61,8 @@ pub enum WidgetType { Row, Space, } -use std::cell::RefCell; -use std::rc::Rc; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WidgetNode { pub(crate) view_id: id, pub(crate) hash: u64, @@ -72,14 +72,12 @@ pub struct WidgetNode { related_ids: Vec, pub widget_type: WidgetType, // Used in things like Row, Column and Container. - pub(crate) children: Vec>>, + //pub(crate) children: Vec>>, } impl PartialEq for WidgetNode { fn eq(&self, other: &Self) -> bool { - self.hash == other.hash - && self.widget_type == other.widget_type - && self.children == other.children + self.hash == other.hash && self.widget_type == other.widget_type } } @@ -90,7 +88,7 @@ impl Default for WidgetNode { hash: 0, related_ids: Vec::new(), widget_type: WidgetType::BaseElement, - children: Vec::new(), + //children: Vec::new(), } } } @@ -120,6 +118,7 @@ impl Drop for WidgetNode { } } */ + impl WidgetNode { pub fn new(view_id: id, widget_type: WidgetType, hash: u64) -> Self { Self { @@ -127,7 +126,7 @@ impl WidgetNode { hash, related_ids: Vec::new(), widget_type, - children: Vec::new(), + //children: Vec::new(), } } @@ -144,15 +143,19 @@ impl WidgetNode { for i in &self.related_ids { let obj = NSObject(*i); unsafe { - //obj.dealloc(); + obj.dealloc(); } } - for i in &self.children { - i.borrow().drop_from_ui(); + match &self.widget_type { + WidgetType::Column(ref children) => { + for i in children { + i.borrow().drop_from_ui(); + } + } + _ => {} } } - pub fn draw(&self, parent: UIView) { use uikit_sys::UIView_UIViewHierarchy; if self.view_id != 0 as id { @@ -161,13 +164,143 @@ impl WidgetNode { } } } + fn is_mergeable(&self, other: &Self) -> bool { + use WidgetType::*; + match (&self.widget_type, &other.widget_type) { + (Text(_), Text(_)) + | (Column(_), Column(_)) + | (TextInput, TextInput) => true, + _ => false, + } + } + + pub fn merge(&mut self, other: &Self, root_view: Option) { + use std::convert::TryInto; + use uikit_sys::{IUIStackView, UIStackView, UIView_UIViewHierarchy}; + + if !self.is_mergeable(other) { + let old_self = self.clone(); + *self = Self { ..other.clone() }; + if let Some(parent) = root_view { + self.draw(parent); + old_self.drop_from_ui(); + /* + + let old_view = UIView(self.view_id); + unsafe { + old_view.removeFromSuperview(); + } + */ + } + old_self.drop_from_ui(); + } else { + match (&mut self.widget_type, &other.widget_type) { + ( + WidgetType::Column(ref mut my_children), + WidgetType::Column(other_children), + ) => { + let stackview = UIStackView(self.view_id); + if my_children.len() == other_children.len() { + for i in 0..my_children.len() { + let current_child = my_children.get_mut(i).unwrap(); + let new_child = other_children.get(i).unwrap(); + + if current_child + .borrow() + .is_mergeable(&new_child.borrow()) + { + current_child + .borrow_mut() + .merge(&new_child.borrow(), None); + } else { + unsafe { + stackview.removeArrangedSubview_( + current_child.borrow().view_id, + ); + current_child.borrow().drop_from_ui(); + stackview.insertArrangedSubview_atIndex_( + new_child.borrow().view_id, + i.try_into().unwrap(), + ); + } + my_children[i] = new_child.clone(); + } + } + } else { + let stackview = uikit_sys::UIStackView(self.view_id); + for i in my_children.clone() { + unsafe { + stackview + .removeArrangedSubview_(i.borrow().view_id) + } + i.borrow().drop_from_ui(); + } + *my_children = other_children.clone(); + for i in my_children { + unsafe { + stackview + .addArrangedSubview_(i.borrow().view_id) + } + } + } + } + ( + WidgetType::Text(current_text), + WidgetType::Text(new_text), + ) => { + debug!( + "Updating text from {} to {}", + current_text, new_text + ); + use std::convert::TryInto; + use std::ffi::CString; + use uikit_sys::{ + IUITextView, NSString, + NSString_NSStringExtensionMethods, + NSUTF8StringEncoding, UITextView, + }; + let label = UITextView(self.view_id); + unsafe { + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(new_text.as_str()) + .expect("CString::new failed") + .as_ptr() + as *mut std::ffi::c_void, + new_text.len().try_into().unwrap(), + NSUTF8StringEncoding, + ), + ); + label.setText_(text.0); + } + } + ( + WidgetType::TextInput, + WidgetType::TextInput, + ) => { + debug!("Updating text input widgets is not implemented yet"); + //TODO: Add stuff about Text Input + } + (me, you) => { + debug!("Widget's don't match! {:?}, {:?}", me, you); + } + } + } + } pub fn add_related_id(&mut self, related_id: id) { self.related_ids.push(related_id); } pub fn add_child(&mut self, child: WidgetNode) { - self.children.push(Rc::new(RefCell::new(child))); + match &mut self.widget_type { + WidgetType::Column(ref mut children) => { + children.push(Rc::new(RefCell::new(child))); + } + e => { + unimplemented!("CHILDREN ARE NOT IMPLEMENTED FOR {:?}", e); + } + } } pub fn add_children(&mut self, _children: Vec) { /* @@ -193,7 +326,7 @@ pub trait Widget { WidgetNode::new(0 as id, self.get_widget_type(), hash) } - fn build_uiview(&self) -> WidgetNode { + fn build_uiview(&self, is_root: bool) -> WidgetNode { unimplemented!( "{:?} using base implementation", self.get_widget_type() @@ -305,8 +438,8 @@ impl<'a, Message> Widget for Element<'a, Message> { self.widget.get_render_action(widget_node) } - fn build_uiview(&self) -> WidgetNode { - self.widget.build_uiview() + fn build_uiview(&self, is_root: bool) -> WidgetNode { + self.widget.build_uiview(is_root) } fn on_widget_event( diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 2357cbe5f8..fd7cca5dd1 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -1,11 +1,25 @@ use crate::{ event::WidgetEvent, - widget::{WidgetNode, WidgetType, RenderAction,}, + widget::{RenderAction, WidgetNode, WidgetType}, Align, Element, Hasher, Length, Widget, }; +use std::cell::RefCell; use std::hash::Hash; +use std::rc::Rc; use std::u32; +use uikit_sys::{ + id, CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, + INSObject, IUIColor, IUIStackView, IUITextView, NSLayoutConstraint, + NSLayoutDimension, UIColor, + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, + UIStackViewAlignment_UIStackViewAlignmentCenter, + UIStackViewDistribution_UIStackViewDistributionFill, UITextView, UIView, + UIView_UIViewGeometry, UIView_UIViewHierarchy, + UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, + UIScreen, + IUIScreen, +}; /// A container that distributes its contents vertically. /// @@ -117,16 +131,6 @@ impl<'a, Message> Column<'a, Message> { self } } -use uikit_sys::{ - id, CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, - INSObject, IUIColor, IUIStackView, IUITextView, NSLayoutConstraint, - NSLayoutDimension, UIColor, - UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, - UIStackViewAlignment_UIStackViewAlignmentCenter, - UIStackViewDistribution_UIStackViewDistributionFill, UITextView, UIView, - UIView_UIViewGeometry, UIView_UIViewHierarchy, - UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, -}; impl<'a, Message> Widget for Column<'a, Message> where @@ -138,12 +142,21 @@ where messages: &mut Vec, widget_node: &WidgetNode, ) { - debug!("on_widget_event for column for {:?} children", self.children.len()); - for (i, node) in - &mut self.children.iter_mut().zip(widget_node.children.iter()) - { - debug!("on_widget_event for {:?} child", i.get_widget_node()); - i.on_widget_event(event.clone(), messages, &node.borrow()); + debug!( + "on_widget_event for column for {:?} children", + self.children.len() + ); + match &widget_node.widget_type { + WidgetType::Column(node_children) => { + for (i, node) in + &mut self.children.iter_mut().zip(node_children) + { + i.on_widget_event(event.clone(), messages, &node.borrow()); + } + } + e => { + error!("Widget tree traversal out of sync. {:?} should be a Column!", e); + } } } @@ -171,41 +184,77 @@ where } fn get_widget_type(&self) -> WidgetType { - WidgetType::Column + WidgetType::Column(Vec::new()) } fn get_widget_node(&self) -> WidgetNode { - let mut node = WidgetNode::new(0 as id, WidgetType::Column, self.get_my_hash()); + /* + let children = self + .children + .into_iter() + .map(|i| Rc::new(RefCell::new(i.get_widget_node()))) + .collect::>>>(); + */ + + let mut children = Vec::new(); + for i in &self.children { + children.push(Rc::new(RefCell::new(i.get_widget_node()))); + } + + let mut node = WidgetNode::new( + 0 as id, + WidgetType::Column(children), + self.get_my_hash(), + ); + /* for i in &self.children { node.add_child(i.get_widget_node()); } + */ node } - fn build_uiview(&self) -> WidgetNode { + fn build_uiview(&self, is_root: bool) -> WidgetNode { let stack_view = unsafe { + + /* let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, size: CGSize { height: 400.0, - width: 300.0, + width: 500.0, }, }; - let stack_view = - UIStackView(UIStackView::alloc().initWithFrame_(rect)); - //let stack_view = UIStackView(UIStackView::alloc().init()); - //stack_view.setFrame_(rect); + */ + //let stack_view = + // UIStackView(UIStackView::alloc().initWithFrame_(rect)); + let stack_view = UIStackView(UIStackView::alloc().init()); + if is_root { + let screen = UIScreen(UIScreen::mainScreen()); + let frame = screen.bounds(); + stack_view.setFrame_(frame); + } + /* + * + * This is for a Row widget. + stack_view.setAxis_( + uikit_sys::UILayoutConstraintAxis_UILayoutConstraintAxisHorizontal, + ); + */ stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - //stack_view .setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFill, ); stack_view }; - let mut stackview_node = - WidgetNode::new(stack_view.0, self.get_widget_type(), self.get_my_hash()); + let mut stackview_node = WidgetNode::new( + stack_view.0, + self.get_widget_type(), + self.get_my_hash(), + ); for (i, val) in self.children.iter().enumerate() { - let node = val.build_uiview(); + let node = val.build_uiview(false); let subview = UIView(node.view_id); stackview_node.add_child(node); unsafe { diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 43db2f1ce5..f1f7628138 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -15,8 +15,11 @@ use std::convert::TryInto; use std::ffi::CString; use uikit_sys::{ CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, - NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, + NSString, NSString_NSStringExtensionMethods, + UIColor, UILabel, UIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, + UIView_UIViewRendering, + UIScreen, IUIScreen, }; use std::marker::PhantomData; @@ -137,11 +140,13 @@ impl Widget for Text { } fn get_widget_type(&self) -> WidgetType { - WidgetType::Text + WidgetType::Text(self.content.clone()) } - fn build_uiview(&self) -> WidgetNode { + fn build_uiview(&self, is_root: bool) -> WidgetNode { let label = unsafe { let label = UILabel::alloc(); + label.init(); + let text = NSString( NSString::alloc().initWithBytes_length_encoding_( CString::new(self.content.as_str()) @@ -151,8 +156,13 @@ impl Widget for Text { uikit_sys::NSUTF8StringEncoding, ), ); - label.init(); label.setText_(text.0); + debug!("THIS TEXT IS A ROOT NODE: {:?}", is_root); + if is_root { + let screen = UIScreen(UIScreen::mainScreen()); + let frame = screen.bounds(); + label.setFrame_(frame); + } /* let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, @@ -165,6 +175,7 @@ impl Widget for Text { */ label.setAdjustsFontSizeToFitWidth_(true); label.setMinimumScaleFactor_(10.0); + label.setClipsToBounds_(true); if let Some(color) = self.color { let background = UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index a6727cd6db..5074210503 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -23,6 +23,8 @@ use uikit_sys::{ NSNotificationCenter, NSString, NSString_NSStringExtensionMethods, UITextView, UITextViewTextDidChangeNotification, UIView, UIView_UIViewHierarchy, + UIView_UIViewGeometry, + UIScreen, IUIScreen, }; /// A field that can be filled with text. @@ -173,11 +175,17 @@ where WidgetType::TextInput } - fn build_uiview(&self) -> WidgetNode { + fn build_uiview(&self, is_root: bool) -> WidgetNode { let textview = unsafe { let ui_textview = { // TODO: Use something better than just a rect. - UITextView(UITextView::alloc().init()) + let view = UITextView(UITextView::alloc().init()); + if is_root { + let screen = UIScreen(UIScreen::mainScreen()); + let frame = screen.bounds(); + view.setFrame_(frame); + } + view /* if parent.is_none() { UITextView(UITextView::alloc().init()) @@ -221,7 +229,7 @@ where widget_node: &WidgetNode, ) { debug!( - "on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, self.widget_id: {:?} widget_node.view_id {:?}", + "on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, widget_node.view_id {:?}", widget_event.id, widget_event.widget_id, widget_node.view_id, From 40a3d7a844110dd9436ab7f1278a039e9c251692 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Fri, 24 Jul 2020 15:02:27 -0700 Subject: [PATCH 22/33] Updates for winit --- ios/Cargo.toml | 4 +++- ios/src/widget/column.rs | 4 +++- ios/src/widget/text.rs | 5 ++++- ios/src/widget/text_input.rs | 5 ++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index f6c5326348..b0aa94b8e8 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -22,7 +22,9 @@ cargo-bundle's support for bundling crate examples. [dependencies] twox-hash = "1.5" raw-window-handle = "^0.3" -winit = "^0.22" +# winit = "^0.22" +# winit = { path = "../../winit/" } +winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring"} objc = "0.2.7" uikit-sys = { git = "https://github.com/simlay/uikit-sys" } log = "0.4" diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index fd7cca5dd1..302615fbcd 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -242,10 +242,12 @@ where stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + //stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + /* stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFill, ); + */ stack_view }; let mut stackview_node = WidgetNode::new( diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index f1f7628138..b1c797c9c5 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -16,9 +16,10 @@ use std::ffi::CString; use uikit_sys::{ CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, NSString, NSString_NSStringExtensionMethods, - UIColor, UILabel, UIView, + UIColor, UILabel, UIView, IUIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, UIView_UIViewRendering, + CALayer, ICALayer, UIScreen, IUIScreen, }; use std::marker::PhantomData; @@ -163,6 +164,8 @@ impl Widget for Text { let frame = screen.bounds(); label.setFrame_(frame); } + let layer = CALayer(label.layer()); + layer.setBorderWidth_(3.0); /* let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 5074210503..306f094dd6 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -21,9 +21,10 @@ use std::convert::TryInto; use uikit_sys::{ id, CGPoint, CGRect, CGSize, INSNotificationCenter, INSObject, IUITextView, NSNotificationCenter, NSString, NSString_NSStringExtensionMethods, - UITextView, UITextViewTextDidChangeNotification, UIView, + UITextView, UITextViewTextDidChangeNotification, UIView, IUIView, UIView_UIViewHierarchy, UIView_UIViewGeometry, + CALayer, ICALayer, UIScreen, IUIScreen, }; @@ -185,6 +186,8 @@ where let frame = screen.bounds(); view.setFrame_(frame); } + let layer = CALayer(view.layer()); + layer.setBorderWidth_(3.0); view /* if parent.is_none() { From 325a4bef08008cebd6af4a247ada1ebe3dc01e9f Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Mon, 27 Jul 2020 13:24:19 -0700 Subject: [PATCH 23/33] Minor updates --- ios/Cargo.toml | 1 + ios/examples/icon32x32.png | Bin 0 -> 2601 bytes ios/examples/ios-example.rs | 4 ++-- ios/src/application.rs | 20 ++++++++++++++------ ios/src/widget/column.rs | 8 +++----- 5 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 ios/examples/icon32x32.png diff --git a/ios/Cargo.toml b/ios/Cargo.toml index b0aa94b8e8..ee4523419e 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -9,6 +9,7 @@ description = "iOS backend for iced" name = "ios-example" identifier = "com.github.iced.simple" category = "Utility" +icon = ["examples/icon32x32.png"] short_description = "An example of a bundled application" long_description = """ A trivial application that just displays a blank window with diff --git a/ios/examples/icon32x32.png b/ios/examples/icon32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f170e8089b2114feb0f93eb1738aacfe424db5b1 GIT binary patch literal 2601 zcmY*bcRU+v_m53fwMLDiLCqjmOCkv&6tx?zeeDV&A#nxuioJ@f!)nS)TQwVEwY6KV zR?SkB)-0~wVHCj|z4v#2@B4f{=XuUK&-Z-4=RAKsDK=J@_<-U-006*eW{S3Bo}gpn z=49@~h=@(*!9ujVWC(cDC$Yo~cnGGBL;!$S@Yq-Y`BYIRz!8gaAURlCz`XD{H4ksR zr;i#LM_{4>05}=Oba6f;4-gp_7(|4T5i-9JFs6T;hRA?^K}Z1z83#)nkP$w_2XsMA zTTNXC2?T*a@DOibm>t^W?{a2@kntmt2rvjFEG$edOiK+Pavh=pg+d|fnh;G*RVG4} z7#>9OAgcxuW&a}iKOVFX(JKT?AYt)ApkrPSPkbl|AtQ4v^w0XMPZHMm|CNG>e~-l+ z5ORD2(NI%|{KL&Gg&${O)*)CQrt&dAQUm@A`9Hb8b>NU=@&9V(uStJpnWG|saL7N; zh6Gx?P;dkQxUptv6vlz4{#qmfb4~2153#eZb?8^Ne%(M#a`FILT2;HpX7gY{=4bO9=CGCoJTkwU@h3W6n}! z=ji97+xnmGu$SXm^KV%9rKG>jOvqhR{Q#Fnhcf`HUr|SC8XtE0WcO#@)V4*uA8Qu# zj^zE?IIH64;83cA56UhKa;Znsk45iM(Tr`sF*7&Cy}RDC&qbRbX)*w z%J7;Pypj-P^~bFxuBNVKG#GzeF;B;0L)vx}w<3viK%K{fHUcwB!!2};y+fAsHGF0& z4#4~(q({K#D^H?0o;`X5%{B~Hxsh*oL8Qmm?ZJF+v77aYGrZKuvd{FCn2EJ^Rc@B- zo!*CzlLwPVdX6mhvCjZEZDZTVjKIbe-NWYSe zz22o{*P3%xPF`-B<5#KQgmKhN#e>=;Xt_#Eu4sZTr&H(6TBsCTO`sCw%RID22WBDH zX#b_^!*1E6V0a@KU|&M*?~$PURF3+S(}y>3UoJf4o;4T3Y@S|WI|odi<&gZ%?6Lfh zj&s~eozO;twUUi~-+n+&F=TOmfo3HLG`{qw5PvF%w=pM2y{X5vsiEy`J4#Bl-nhK; z*-T)?{#cr`a@3JdOg6er=UvkHh<;Wc$Hdlhh!1?cH_1!ql!hdaB1Pg}XU^Bv>fqm-v1UDbr-S+>Vd<-*;Rnw@ zyia%V+;V4EG)?8I`{a~23S@MS$t|Yb|60rFW~5xzd(&u4+0qmgz;M+UEQKwj+yHu< zXX5*>rqbqpMI2seD5vjDy($I+Y*XV@q{%NLrQY@wZB|{?Hk1#Id|oV)X+6%8?KbJ8 zBW8T|D8<0ev_Rj{vCSha*iB0SYqx$t?n{DPHGDIs=i@dMpIZc9^h(2SIoQbl-%VPg#;p8 z4nzor?JE`QP>_KXrwNZ%XvhKm}{klB9fdBxL{yg z7$i-94`+Os(-yQ`olpPX#3}oovV+IG6b6-U29Jr6g*q%j?QwJqSJJ28-S;(;BacTm zHaBlP6Cq2fL$|q9 z1v44g;MQkPu8Vdy%<#r$``3(N*xAn;eDf%{hnnuL7IGb)TRyA*{8hDNJ5g$bUHXiz zuLAVXELqv^+3W3gSKK~Vrc_ztm&e(zNGYgVL6R6$xY?lfB0+q8BCk*@D-i*fa%(t` zyEpOj^5R|u$M8k=FMjAp3cMXT%C^{8PDbDEJ3O{9jGug%>T@=*g^ikJirH&e z{C5#3U@ix_iW52T^yCFV9&N^RL6u3R+;VcZ<~mp@_tS z@Ju^!0!22B5)$sO3p4keRSB;9MstZEF7Uy@&D(OaV$U@szqZ14R{U&q`D&) z;@mr>uT;hCxqP3V(9dNo(ff@?U+KhpjNOelM-a)jm?r-I!rd;G4J~A&t|7a5O^%PP ze0g%}84=-CoO}iADSL{a3RTx>h!mi{dNG$b!vLgReyR`6R!iULkqTootdFkPUQe|) zj@iB#=Y&fl<7;&<66D8jUwZ->bWSj)Cu80D9tc$OEQG>m>QTxlHOX1uH714@PHDDi%Kve7I?cOGFygQYCjp zfpTj5>gpfeYca9e)?AJKEI)$zA11_ zuSTxgJRqI|H{F_F+k4q_iSsN?T+=VXq|l`HX2lnY03MOlpQoGoElj|eKQ#8Y=Nja5_3l!H%$4@-2;}Y?C9?W0Ud(G5 z3M${Y!#?)9<9C`1j+U`WeTrOQxVLsSytpMfZz$JF$&8;v1$9H$-~nN)^t)qy{2#3P zSW47|+YvO6;E?&>Tmw-?E7>?rZREI(=#v@|3qm{2=(MWo*3l#)u0-A7DgvuD8 { self.text = val; diff --git a/ios/src/application.rs b/ios/src/application.rs index 6df654479c..5ebfb98b50 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -6,8 +6,15 @@ use crate::{ use winit::{ event, event_loop::{ControlFlow, EventLoop}, - platform::ios::WindowExtIOS, - window::WindowBuilder, + platform::ios::{ + WindowExtIOS + }, + monitor::MonitorHandle, + window::{ + WindowBuilder, + Window, + Fullscreen, + }, }; use uikit_sys::{ @@ -124,12 +131,13 @@ pub trait Application: Sized { let window = { let mut window_builder = WindowBuilder::new(); - //let (width, height) = settings.window.size; - + //let (width, height) = settings.window.size + //let window = Window::new(&event_loop).unwrap(); window_builder = window_builder .with_title(title) - .with_maximized(true) - //.with_fullscreen(None) + //.with_maximized(true) + //.with_decorations(false) + //.with_fullscreen()//Some(Fullscreen::Borderless(window.current_monitor()))) //.with_inner_size(winit::dpi::LogicalSize { width: 1000, height: 1000}) ; /* diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 302615fbcd..3b4e921d50 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -242,12 +242,10 @@ where stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - //stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); - /* + stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFill, ); - */ stack_view }; let mut stackview_node = WidgetNode::new( @@ -261,10 +259,10 @@ where stackview_node.add_child(node); unsafe { let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + NSLayoutConstraint(layout.constraintEqualToConstant_(10.0)) .setActive_(true); let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(100.0)) + NSLayoutConstraint(layout.constraintEqualToConstant_(10.0)) .setActive_(true); stack_view.addArrangedSubview_(subview.0); } From 9c20c700360eeefc7880543609e94357d3f81a97 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Mon, 27 Jul 2020 14:41:51 -0700 Subject: [PATCH 24/33] Testing out stuff for rust-bindgen 1835 --- ios/Cargo.toml | 4 ++-- ios/src/application.rs | 4 ++-- ios/src/widget.rs | 12 ++++++------ ios/src/widget/column.rs | 16 +++++++--------- ios/src/widget/text.rs | 12 ++++++------ ios/src/widget/text_input.rs | 9 ++++----- 6 files changed, 27 insertions(+), 30 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index ee4523419e..e690a756cf 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -27,10 +27,10 @@ raw-window-handle = "^0.3" # winit = { path = "../../winit/" } winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring"} objc = "0.2.7" -uikit-sys = { git = "https://github.com/simlay/uikit-sys" } log = "0.4" num-traits = "0.2" -#uikit-sys = { path = "../../uikit-sys/" } +uikit-sys = { path = "../../uikit-sys/" } +#uikit-sys = { git = "https://github.com/simlay/uikit-sys" } [dependencies.iced_core] version = "0.2" diff --git a/ios/src/application.rs b/ios/src/application.rs index 5ebfb98b50..b55635675f 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -149,9 +149,9 @@ pub trait Application: Sized { let root_view: UIView = UIView(window.ui_view() as id); unsafe { - let background = UIColor(UIColor::greenColor()); + let background = UIColor::greenColor(); //let background = UIColor(UIColor::whiteColor()); - root_view.setBackgroundColor_(background.0); + root_view.setBackgroundColor_(background); /* let rect = CGRect { origin: CGPoint { x: 0.0, y: 0.0 }, diff --git a/ios/src/widget.rs b/ios/src/widget.rs index e3ee9c9279..3ebcbf92da 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -160,7 +160,7 @@ impl WidgetNode { use uikit_sys::UIView_UIViewHierarchy; if self.view_id != 0 as id { unsafe { - parent.addSubview_(self.view_id); + parent.addSubview_(UIView(self.view_id)); } } } @@ -215,11 +215,11 @@ impl WidgetNode { } else { unsafe { stackview.removeArrangedSubview_( - current_child.borrow().view_id, + UIView(current_child.borrow().view_id) ); current_child.borrow().drop_from_ui(); stackview.insertArrangedSubview_atIndex_( - new_child.borrow().view_id, + UIView(new_child.borrow().view_id), i.try_into().unwrap(), ); } @@ -231,7 +231,7 @@ impl WidgetNode { for i in my_children.clone() { unsafe { stackview - .removeArrangedSubview_(i.borrow().view_id) + .removeArrangedSubview_(UIView(i.borrow().view_id)) } i.borrow().drop_from_ui(); } @@ -239,7 +239,7 @@ impl WidgetNode { for i in my_children { unsafe { stackview - .addArrangedSubview_(i.borrow().view_id) + .addArrangedSubview_(UIView(i.borrow().view_id)) } } } @@ -271,7 +271,7 @@ impl WidgetNode { NSUTF8StringEncoding, ), ); - label.setText_(text.0); + label.setText_(text); } } ( diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 3b4e921d50..60593cda47 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -228,7 +228,7 @@ where // UIStackView(UIStackView::alloc().initWithFrame_(rect)); let stack_view = UIStackView(UIStackView::alloc().init()); if is_root { - let screen = UIScreen(UIScreen::mainScreen()); + let screen = UIScreen::mainScreen(); let frame = screen.bounds(); stack_view.setFrame_(frame); } @@ -242,7 +242,7 @@ where stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + //stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFill, ); @@ -258,13 +258,11 @@ where let subview = UIView(node.view_id); stackview_node.add_child(node); unsafe { - let layout = NSLayoutDimension(subview.heightAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(10.0)) - .setActive_(true); - let layout = NSLayoutDimension(subview.widthAnchor()); - NSLayoutConstraint(layout.constraintEqualToConstant_(10.0)) - .setActive_(true); - stack_view.addArrangedSubview_(subview.0); + let layout = subview.heightAnchor(); + layout.constraintEqualToConstant_(100.0).setActive_(true); + let layout = subview.widthAnchor(); + layout.constraintEqualToConstant_(100.0).setActive_(true); + stack_view.addArrangedSubview_(subview); } } stackview_node diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index b1c797c9c5..6e51435a79 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -157,14 +157,14 @@ impl Widget for Text { uikit_sys::NSUTF8StringEncoding, ), ); - label.setText_(text.0); + label.setText_(text); debug!("THIS TEXT IS A ROOT NODE: {:?}", is_root); if is_root { - let screen = UIScreen(UIScreen::mainScreen()); + let screen = UIScreen::mainScreen(); let frame = screen.bounds(); label.setFrame_(frame); } - let layer = CALayer(label.layer()); + let layer = label.layer(); layer.setBorderWidth_(3.0); /* let rect = CGRect { @@ -181,13 +181,13 @@ impl Widget for Text { label.setClipsToBounds_(true); if let Some(color) = self.color { let background = - UIColor(UIColor::alloc().initWithRed_green_blue_alpha_( + UIColor::alloc().initWithRed_green_blue_alpha_( color.r.into(), color.g.into(), color.b.into(), color.a.into(), - )); - label.setTextColor_(background.0) + ); + label.setTextColor_(background) } label }; diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 306f094dd6..46bef2efa2 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -182,11 +182,11 @@ where // TODO: Use something better than just a rect. let view = UITextView(UITextView::alloc().init()); if is_root { - let screen = UIScreen(UIScreen::mainScreen()); + let screen = UIScreen::mainScreen(); let frame = screen.bounds(); view.setFrame_(frame); } - let layer = CALayer(view.layer()); + let layer = view.layer(); layer.setBorderWidth_(3.0); view /* @@ -209,8 +209,7 @@ where }; let on_change = EventHandler::new(ui_textview.0); // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc - let center = - NSNotificationCenter(NSNotificationCenter::defaultCenter()); + let center = NSNotificationCenter::defaultCenter(); center.addObserver_selector_name_object_( on_change.id, sel!(sendEvent), @@ -240,7 +239,7 @@ where if widget_event.id as id == widget_node.view_id { let ui_textview = UITextView(widget_event.id as id); let value = unsafe { - let value = NSString(ui_textview.text()); + let value = ui_textview.text(); let len = value .lengthOfBytesUsingEncoding_(uikit_sys::NSUTF8StringEncoding); let bytes = value.UTF8String() as *const u8; From 510a273d2fb93fde2bb441aaaf32c5426dd8f8e8 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Fri, 31 Jul 2020 00:31:03 -0700 Subject: [PATCH 25/33] Tested out using a closure as a poorly thought out factory pattern --- ios/examples/ios-example.rs | 23 ++++++- ios/src/application.rs | 16 ++--- ios/src/widget.rs | 117 ++++++++++++++--------------------- ios/src/widget/column.rs | 101 ++++++++++++++++-------------- ios/src/widget/text.rs | 93 +++++++++++++++------------- ios/src/widget/text_input.rs | 39 +++++------- 6 files changed, 198 insertions(+), 191 deletions(-) diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index c2ab01d865..fbcb0d0400 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -75,6 +75,9 @@ impl Sandbox for Simple { ).into() } else { let column = Column::new() + .push( + Text::new(format!("FOO: {}", &self.text)).color(Color::BLACK) + ) .push( TextInput::new( &mut self.text_state, @@ -86,7 +89,25 @@ impl Sandbox for Simple { ) ) .push( - Text::new(format!("TEXT2: {}", &self.text)).color(Color::BLACK) + Text::new(format!("BAR: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) + ) + .push( + Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) ) ; column.into() diff --git a/ios/src/application.rs b/ios/src/application.rs index b55635675f..e5d6f0882a 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -1,6 +1,6 @@ use crate::{ event::{EventHandler, WidgetEvent}, - widget::{RenderAction, Widget, WidgetNode}, + widget::{Widget, WidgetNode}, Command, Element, Executor, Runtime, Subscription, }; use winit::{ @@ -135,8 +135,7 @@ pub trait Application: Sized { //let window = Window::new(&event_loop).unwrap(); window_builder = window_builder .with_title(title) - //.with_maximized(true) - //.with_decorations(false) + .with_decorations(true) //.with_fullscreen()//Some(Fullscreen::Borderless(window.current_monitor()))) //.with_inner_size(winit::dpi::LogicalSize { width: 1000, height: 1000}) ; @@ -163,7 +162,7 @@ pub trait Application: Sized { root_view.setFrame_(rect); */ } - let mut widget_tree: WidgetNode = app.view().get_widget_node(); + let mut widget_tree: WidgetNode = app.view().build_uiview(true); event_loop.run( move |event: winit::event::Event, _, control_flow| { @@ -180,7 +179,7 @@ pub trait Application: Sized { &mut messages, &widget_tree, ); - debug!("Root widget before: {:?}", widget_tree); + //debug!("Root widget before: {:?}", widget_tree); } for message in messages { let (command, subscription) = runtime.enter(|| { @@ -193,10 +192,11 @@ pub trait Application: Sized { runtime.spawn(command); runtime.track(subscription); } - let mut element = app.view(); + let element = app.view(); + //widget_tree = element.update(widget_tree, Some(root_view)); let new_tree = element.build_uiview(true); widget_tree.merge(&new_tree, Some(root_view)); - debug!("Root widget after: {:#?}", widget_tree); + //debug!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { @@ -204,7 +204,7 @@ pub trait Application: Sized { .. } => {} event::Event::NewEvents(event::StartCause::Init) => { - let mut element = app.view(); + let element = app.view(); widget_tree = element.build_uiview(true); let root_view: UIView = UIView(window.ui_view() as id); widget_tree.draw(root_view); diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 3ebcbf92da..766fa402dd 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -2,6 +2,7 @@ use crate::{event::WidgetEvent, Hasher, Length}; use std::cell::RefCell; use std::rc::Rc; use uikit_sys::{id, UIView}; +use std::convert::TryInto; /* pub mod button; @@ -62,36 +63,35 @@ pub enum WidgetType { Space, } -#[derive(Debug, Clone)] pub struct WidgetNode { pub(crate) view_id: id, pub(crate) hash: u64, + pub(crate) uikit_builder: Rc id>>, //pub (crate) widget_id: u64, // Used for memory collection. related_ids: Vec, pub widget_type: WidgetType, - // Used in things like Row, Column and Container. - //pub(crate) children: Vec>>, } +use std::fmt; +impl fmt::Debug for WidgetNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WidgetNode") + .field("view_id", &self.view_id) + .field("hash", &self.hash) + .field("widget_type", &self.widget_type) + .finish() + } +} + + impl PartialEq for WidgetNode { fn eq(&self, other: &Self) -> bool { self.hash == other.hash && self.widget_type == other.widget_type } } -impl Default for WidgetNode { - fn default() -> Self { - Self { - view_id: 0 as id, - hash: 0, - related_ids: Vec::new(), - widget_type: WidgetType::BaseElement, - //children: Vec::new(), - } - } -} /* impl Drop for WidgetNode { @@ -120,10 +120,11 @@ impl Drop for WidgetNode { */ impl WidgetNode { - pub fn new(view_id: id, widget_type: WidgetType, hash: u64) -> Self { + pub fn new(uikit_builder: Rc id>>, widget_type: WidgetType, hash: u64) -> Self { Self { - view_id, + view_id: 0 as id, hash, + uikit_builder, related_ids: Vec::new(), widget_type, //children: Vec::new(), @@ -164,6 +165,7 @@ impl WidgetNode { } } } + fn is_mergeable(&self, other: &Self) -> bool { use WidgetType::*; match (&self.widget_type, &other.widget_type) { @@ -175,24 +177,18 @@ impl WidgetNode { } pub fn merge(&mut self, other: &Self, root_view: Option) { - use std::convert::TryInto; use uikit_sys::{IUIStackView, UIStackView, UIView_UIViewHierarchy}; if !self.is_mergeable(other) { + /* let old_self = self.clone(); *self = Self { ..other.clone() }; if let Some(parent) = root_view { self.draw(parent); old_self.drop_from_ui(); - /* - - let old_view = UIView(self.view_id); - unsafe { - old_view.removeFromSuperview(); - } - */ } old_self.drop_from_ui(); + */ } else { match (&mut self.widget_type, &other.widget_type) { ( @@ -212,6 +208,7 @@ impl WidgetNode { current_child .borrow_mut() .merge(&new_child.borrow(), None); + new_child.borrow().drop_from_ui(); } else { unsafe { stackview.removeArrangedSubview_( @@ -245,14 +242,9 @@ impl WidgetNode { } } ( - WidgetType::Text(current_text), + WidgetType::Text(_current_text), WidgetType::Text(new_text), ) => { - debug!( - "Updating text from {} to {}", - current_text, new_text - ); - use std::convert::TryInto; use std::ffi::CString; use uikit_sys::{ IUITextView, NSString, @@ -292,6 +284,20 @@ impl WidgetNode { self.related_ids.push(related_id); } + fn replace_child(&mut self, child: WidgetNode, i: usize) { + match &mut self.widget_type { + WidgetType::Column(ref mut children) => { + if i < children.len() { + children[i] = Rc::new(RefCell::new(child)); + } + } + e => { + unimplemented!("CHILDREN ARE NOT IMPLEMENTED FOR {:?}", e); + } + } + + } + pub fn add_child(&mut self, child: WidgetNode) { match &mut self.widget_type { WidgetType::Column(ref mut children) => { @@ -311,22 +317,17 @@ impl WidgetNode { } } -#[derive(Debug)] -pub enum RenderAction { - Add, - Remove, - Update, -} - pub trait Widget { fn get_widget_type(&self) -> WidgetType; - fn get_widget_node(&self) -> WidgetNode { - let hash = self.get_my_hash(); - WidgetNode::new(0 as id, self.get_widget_type(), hash) + fn build_uiview(&self, _is_root: bool) -> WidgetNode { + unimplemented!( + "{:?} using base implementation", + self.get_widget_type() + ); } - fn build_uiview(&self, is_root: bool) -> WidgetNode { + fn update(&self, current_node: WidgetNode, root_view: Option) -> WidgetNode { unimplemented!( "{:?} using base implementation", self.get_widget_type() @@ -352,28 +353,7 @@ pub trait Widget { //_renderer: &Renderer, //_clipboard: Option<&dyn Clipboard>, ) { - debug!("on_widget_event for {:?}", self.get_widget_type()); - } - - fn get_render_action( - &self, - widget_node: Option<&WidgetNode>, - ) -> RenderAction { - let action = if widget_node.is_none() { - RenderAction::Add - } else if widget_node.is_some() - && widget_node.unwrap().widget_type == self.get_widget_type() - { - RenderAction::Update - } else { - RenderAction::Remove - }; - debug!( - "RENDER ACTION FOR WIDGET {:?} is {:?}", - self.get_widget_type(), - action - ); - action + //debug!("on_widget_event for {:?}", self.get_widget_type()); } fn width(&self) -> Length; @@ -420,6 +400,11 @@ impl<'a, Message> Widget for Element<'a, Message> { self.widget.hash_layout(state); } + fn update(&self, current_node: WidgetNode, root_view: Option) -> WidgetNode { + self.widget.update(current_node, root_view) + } + + fn width(&self) -> Length { self.widget.width() } @@ -431,12 +416,6 @@ impl<'a, Message> Widget for Element<'a, Message> { fn get_widget_type(&self) -> WidgetType { self.widget.get_widget_type() } - fn get_render_action( - &self, - widget_node: Option<&WidgetNode>, - ) -> RenderAction { - self.widget.get_render_action(widget_node) - } fn build_uiview(&self, is_root: bool) -> WidgetNode { self.widget.build_uiview(is_root) diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 60593cda47..b4ad0f63ff 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -1,6 +1,6 @@ use crate::{ event::WidgetEvent, - widget::{RenderAction, WidgetNode, WidgetType}, + widget::{WidgetNode, WidgetType}, Align, Element, Hasher, Length, Widget, }; use std::cell::RefCell; @@ -14,10 +14,12 @@ use uikit_sys::{ NSLayoutDimension, UIColor, UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, UIStackViewAlignment_UIStackViewAlignmentCenter, - UIStackViewDistribution_UIStackViewDistributionFill, UITextView, UIView, + UIStackViewDistribution_UIStackViewDistributionFillEqually, UITextView, UIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, UIScreen, + IUIView, + ICALayer, IUIScreen, }; @@ -182,50 +184,36 @@ where fn height(&self) -> Length { self.height } + fn update(&self, mut current_node: WidgetNode, root_view: Option) -> WidgetNode { + /* + match current_node.widget_type { + WidgetType::Column(ref other_children) => { + //if self.children.len() == other_children.len() { + for (i, (child, node)) in + self.children.iter().zip(other_children).enumerate() + { + current_node.replace_child(child.update((*node).into_inner(), None), i); + //node = &Rc::new(RefCell::new(child.update(node.into_inner(), None))); + } + current_node + }, + other => { + debug!("current node outdate! {:?}", other); + self.build_uiview(root_view.is_some()) + } + } + */ + self.build_uiview(root_view.is_some()) + + } fn get_widget_type(&self) -> WidgetType { WidgetType::Column(Vec::new()) } - fn get_widget_node(&self) -> WidgetNode { - /* - let children = self - .children - .into_iter() - .map(|i| Rc::new(RefCell::new(i.get_widget_node()))) - .collect::>>>(); - */ - let mut children = Vec::new(); - for i in &self.children { - children.push(Rc::new(RefCell::new(i.get_widget_node()))); - } - - let mut node = WidgetNode::new( - 0 as id, - WidgetType::Column(children), - self.get_my_hash(), - ); - /* - for i in &self.children { - node.add_child(i.get_widget_node()); - } - */ - node - } fn build_uiview(&self, is_root: bool) -> WidgetNode { - let stack_view = unsafe { - - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 400.0, - width: 500.0, - }, - }; - */ - //let stack_view = - // UIStackView(UIStackView::alloc().initWithFrame_(rect)); + let mut children : Vec = self.children.iter().map(|child| child.build_uiview(false)).collect(); + let stackview_builder = move || unsafe { let stack_view = UIStackView(UIStackView::alloc().init()); if is_root { let screen = UIScreen::mainScreen(); @@ -242,29 +230,52 @@ where stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - //stack_view.setAlignment_(UIStackViewAlignment_UIStackViewAlignmentCenter); + stack_view.setAlignment_(uikit_sys::UIStackViewAlignment_UIStackViewAlignmentFill); stack_view.setDistribution_( - UIStackViewDistribution_UIStackViewDistributionFill, + UIStackViewDistribution_UIStackViewDistributionFillEqually, ); - stack_view + /* + for child in children { + let view_id = (child.uikit_builder.borrow_mut())(); + let subview = UIView(view_id); + /* + let layout = subview.heightAnchor(); + layout.constraintEqualToConstant_(100.0).setActive_(true); + let layout = subview.widthAnchor(); + layout.constraintEqualToConstant_(100.0).setActive_(true); + */ + stack_view.addArrangedSubview_(subview); + } + */ + stack_view.0 }; let mut stackview_node = WidgetNode::new( - stack_view.0, + Rc::new(RefCell::new( + stackview_builder + )), + //stack_view.0, self.get_widget_type(), self.get_my_hash(), ); - for (i, val) in self.children.iter().enumerate() { + for child in &self.children { + stackview_node.add_child(child.build_uiview(false)); + } + /* + for val in self.children.iter() { let node = val.build_uiview(false); let subview = UIView(node.view_id); stackview_node.add_child(node); unsafe { + /* let layout = subview.heightAnchor(); layout.constraintEqualToConstant_(100.0).setActive_(true); let layout = subview.widthAnchor(); layout.constraintEqualToConstant_(100.0).setActive_(true); + */ stack_view.addArrangedSubview_(subview); } } +*/ stackview_node } } diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 6e51435a79..44e2597f8b 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -8,20 +8,25 @@ use crate::{ Length, VerticalAlignment, Widget, - widget::{WidgetNode, WidgetType, RenderAction}, + widget::{WidgetNode, WidgetType}, Hasher, Size, }; use std::convert::TryInto; use std::ffi::CString; use uikit_sys::{ + id, CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, IUIView, UIView_UIViewGeometry, UIView_UIViewHierarchy, UIView_UIViewRendering, - CALayer, ICALayer, + ICALayer, UIScreen, IUIScreen, }; +use std::{ + cell::RefCell, + rc::Rc, +}; use std::marker::PhantomData; /// A paragraph of text. @@ -144,54 +149,54 @@ impl Widget for Text { WidgetType::Text(self.content.clone()) } fn build_uiview(&self, is_root: bool) -> WidgetNode { - let label = unsafe { - let label = UILabel::alloc(); - label.init(); - - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(self.content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - self.content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.setText_(text); - debug!("THIS TEXT IS A ROOT NODE: {:?}", is_root); - if is_root { - let screen = UIScreen::mainScreen(); - let frame = screen.bounds(); - label.setFrame_(frame); - } - let layer = label.layer(); - layer.setBorderWidth_(3.0); - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 0.0, - width: 0.0, - }, - }; - label.setFrame_(rect); - */ - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(10.0); - label.setClipsToBounds_(true); - if let Some(color) = self.color { - let background = - UIColor::alloc().initWithRed_green_blue_alpha_( + let content = self.content.clone(); + let color = self.color.clone(); + let label_builder = move || { + unsafe { + let label = UILabel::alloc(); + label.init(); + + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.setText_(text); + debug!("THIS TEXT IS A ROOT NODE: {:?}", is_root); + if is_root { + let screen = UIScreen::mainScreen(); + let frame = screen.bounds(); + label.setFrame_(frame); + } + + let layer = label.layer(); + layer.setBorderWidth_(3.0); + + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(10.0); + label.setClipsToBounds_(true); + if let Some(color) = color { + let background = + UIColor::alloc().initWithRed_green_blue_alpha_( color.r.into(), color.g.into(), color.b.into(), color.a.into(), - ); - label.setTextColor_(background) + ); + label.setTextColor_(background) + } + label.0 } - label }; - WidgetNode::new(label.0, self.get_widget_type(), self.get_my_hash()) + WidgetNode::new( + Rc::new(RefCell::new(label_builder)), + self.get_widget_type(), + self.get_my_hash() + ) } fn width(&self) -> Length { self.width diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 46bef2efa2..bcfcda1312 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -15,7 +15,11 @@ use crate::{ pub use iced_style::text_input::{Style, StyleSheet}; -use std::{rc::Rc, u32}; +use std::{ + cell::RefCell, + rc::Rc, + u32 +}; use std::convert::TryInto; use uikit_sys::{ @@ -24,7 +28,7 @@ use uikit_sys::{ UITextView, UITextViewTextDidChangeNotification, UIView, IUIView, UIView_UIViewHierarchy, UIView_UIViewGeometry, - CALayer, ICALayer, + CALayer, UIScreen, IUIScreen, }; @@ -177,35 +181,17 @@ where } fn build_uiview(&self, is_root: bool) -> WidgetNode { - let textview = unsafe { + let textview_builder = move || unsafe { let ui_textview = { // TODO: Use something better than just a rect. let view = UITextView(UITextView::alloc().init()); + debug!("THIS UITEXT VIEW IS A ROOT? {:?}", is_root); if is_root { let screen = UIScreen::mainScreen(); let frame = screen.bounds(); view.setFrame_(frame); } - let layer = view.layer(); - layer.setBorderWidth_(3.0); view - /* - if parent.is_none() { - UITextView(UITextView::alloc().init()) - } else { - let input_rect = CGRect { - origin: CGPoint { x: 10.0, y: 10.0 }, - size: CGSize { - width: 100.0, - height: 100.0, - }, - }; - UITextView(UITextView::alloc().initWithFrame_textContainer_( - input_rect, - 0 as id, - )) - } - */ }; let on_change = EventHandler::new(ui_textview.0); // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc @@ -216,11 +202,16 @@ where UITextViewTextDidChangeNotification, ui_textview.0, ); - ui_textview + ui_textview.0 }; - WidgetNode::new(textview.0, self.get_widget_type(), self.get_my_hash()) + WidgetNode::new( + Rc::new(RefCell::new(textview_builder)), + //textview.0, + self.get_widget_type(), + self.get_my_hash() + ) } From 665b66f26899caa2c69e1aedbc84b598d925bdc9 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Fri, 31 Jul 2020 01:36:32 -0700 Subject: [PATCH 26/33] Removed closure in widgetnode --- ios/examples/ios-example.rs | 2 + ios/src/application.rs | 10 +++-- ios/src/widget.rs | 48 +++++++++++----------- ios/src/widget/column.rs | 40 ++++--------------- ios/src/widget/text.rs | 77 +++++++++++++++++------------------- ios/src/widget/text_input.rs | 10 ++--- 6 files changed, 81 insertions(+), 106 deletions(-) diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index fbcb0d0400..b021c8de72 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -91,6 +91,7 @@ impl Sandbox for Simple { .push( Text::new(format!("BAR: {}", &self.text)).color(Color::BLACK) ) + /* .push( Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) ) @@ -109,6 +110,7 @@ impl Sandbox for Simple { .push( Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) ) + */ ; column.into() } diff --git a/ios/src/application.rs b/ios/src/application.rs index e5d6f0882a..36884130c0 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -162,7 +162,7 @@ pub trait Application: Sized { root_view.setFrame_(rect); */ } - let mut widget_tree: WidgetNode = app.view().build_uiview(true); + let mut widget_tree: WidgetNode = WidgetNode::new(0 as id, crate::widget::WidgetType::BaseElement, 0); event_loop.run( move |event: winit::event::Event, _, control_flow| { @@ -181,6 +181,7 @@ pub trait Application: Sized { ); //debug!("Root widget before: {:?}", widget_tree); } + debug!("NEW MESSAGES! {:?}", messages); for message in messages { let (command, subscription) = runtime.enter(|| { let command = app.update(message); @@ -192,10 +193,10 @@ pub trait Application: Sized { runtime.spawn(command); runtime.track(subscription); } - let element = app.view(); - //widget_tree = element.update(widget_tree, Some(root_view)); - let new_tree = element.build_uiview(true); + let new_tree = app.view().build_uiview(true); widget_tree.merge(&new_tree, Some(root_view)); + + //widget_tree = element.update(widget_tree, Some(root_view)); //debug!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} @@ -204,6 +205,7 @@ pub trait Application: Sized { .. } => {} event::Event::NewEvents(event::StartCause::Init) => { + debug!("WINDOW INIT!"); let element = app.view(); widget_tree = element.build_uiview(true); let root_view: UIView = UIView(window.ui_view() as id); diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 766fa402dd..291de45b2f 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -63,10 +63,11 @@ pub enum WidgetType { Space, } +#[derive(Clone)] pub struct WidgetNode { pub(crate) view_id: id, pub(crate) hash: u64, - pub(crate) uikit_builder: Rc id>>, + //pub(crate) uikit_builder: Rc id>>, //pub (crate) widget_id: u64, // Used for memory collection. @@ -120,14 +121,12 @@ impl Drop for WidgetNode { */ impl WidgetNode { - pub fn new(uikit_builder: Rc id>>, widget_type: WidgetType, hash: u64) -> Self { + pub fn new(view_id: id, widget_type: WidgetType, hash: u64) -> Self { Self { - view_id: 0 as id, + view_id, hash, - uikit_builder, related_ids: Vec::new(), widget_type, - //children: Vec::new(), } } @@ -180,7 +179,6 @@ impl WidgetNode { use uikit_sys::{IUIStackView, UIStackView, UIView_UIViewHierarchy}; if !self.is_mergeable(other) { - /* let old_self = self.clone(); *self = Self { ..other.clone() }; if let Some(parent) = root_view { @@ -188,7 +186,6 @@ impl WidgetNode { old_self.drop_from_ui(); } old_self.drop_from_ui(); - */ } else { match (&mut self.widget_type, &other.widget_type) { ( @@ -242,28 +239,31 @@ impl WidgetNode { } } ( - WidgetType::Text(_current_text), + WidgetType::Text(current_text), WidgetType::Text(new_text), ) => { - use std::ffi::CString; - use uikit_sys::{ - IUITextView, NSString, - NSString_NSStringExtensionMethods, - NSUTF8StringEncoding, UITextView, - }; - let label = UITextView(self.view_id); - unsafe { - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(new_text.as_str()) + if current_text != new_text { + debug!("UPDATING TEXT WIDGET TO {:?}", new_text); + use std::ffi::CString; + use uikit_sys::{ + IUITextView, NSString, + NSString_NSStringExtensionMethods, + NSUTF8StringEncoding, UITextView, + }; + let label = UITextView(self.view_id); + unsafe { + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(new_text.as_str()) .expect("CString::new failed") .as_ptr() as *mut std::ffi::c_void, - new_text.len().try_into().unwrap(), - NSUTF8StringEncoding, - ), - ); - label.setText_(text); + new_text.len().try_into().unwrap(), + NSUTF8StringEncoding, + ), + ); + label.setText_(text); + } } } ( diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index b4ad0f63ff..4729356459 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -184,6 +184,7 @@ where fn height(&self) -> Length { self.height } + /* fn update(&self, mut current_node: WidgetNode, root_view: Option) -> WidgetNode { /* match current_node.widget_type { @@ -206,14 +207,14 @@ where self.build_uiview(root_view.is_some()) } +*/ fn get_widget_type(&self) -> WidgetType { WidgetType::Column(Vec::new()) } fn build_uiview(&self, is_root: bool) -> WidgetNode { - let mut children : Vec = self.children.iter().map(|child| child.build_uiview(false)).collect(); - let stackview_builder = move || unsafe { + let stackview = unsafe { let stack_view = UIStackView(UIStackView::alloc().init()); if is_root { let screen = UIScreen::mainScreen(); @@ -234,48 +235,21 @@ where stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFillEqually, ); - /* - for child in children { - let view_id = (child.uikit_builder.borrow_mut())(); - let subview = UIView(view_id); - /* - let layout = subview.heightAnchor(); - layout.constraintEqualToConstant_(100.0).setActive_(true); - let layout = subview.widthAnchor(); - layout.constraintEqualToConstant_(100.0).setActive_(true); - */ - stack_view.addArrangedSubview_(subview); - } - */ - stack_view.0 + stack_view }; let mut stackview_node = WidgetNode::new( - Rc::new(RefCell::new( - stackview_builder - )), - //stack_view.0, + stackview.0, self.get_widget_type(), self.get_my_hash(), ); for child in &self.children { - stackview_node.add_child(child.build_uiview(false)); - } - /* - for val in self.children.iter() { - let node = val.build_uiview(false); + let node = child.build_uiview(false); let subview = UIView(node.view_id); stackview_node.add_child(node); unsafe { - /* - let layout = subview.heightAnchor(); - layout.constraintEqualToConstant_(100.0).setActive_(true); - let layout = subview.widthAnchor(); - layout.constraintEqualToConstant_(100.0).setActive_(true); - */ - stack_view.addArrangedSubview_(subview); + stackview.addArrangedSubview_(subview); } } -*/ stackview_node } } diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 44e2597f8b..fb9632c592 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -151,49 +151,46 @@ impl Widget for Text { fn build_uiview(&self, is_root: bool) -> WidgetNode { let content = self.content.clone(); let color = self.color.clone(); - let label_builder = move || { - unsafe { - let label = UILabel::alloc(); - label.init(); - - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, - ), - ); - label.setText_(text); - debug!("THIS TEXT IS A ROOT NODE: {:?}", is_root); - if is_root { - let screen = UIScreen::mainScreen(); - let frame = screen.bounds(); - label.setFrame_(frame); - } - - let layer = label.layer(); - layer.setBorderWidth_(3.0); - - label.setAdjustsFontSizeToFitWidth_(true); - label.setMinimumScaleFactor_(10.0); - label.setClipsToBounds_(true); - if let Some(color) = color { - let background = - UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - ); - label.setTextColor_(background) - } - label.0 + let label = unsafe { + let label = UILabel::alloc(); + label.init(); + + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void, + content.len().try_into().unwrap(), + uikit_sys::NSUTF8StringEncoding, + ), + ); + label.setText_(text); + if is_root { + let screen = UIScreen::mainScreen(); + let frame = screen.bounds(); + label.setFrame_(frame); } + + let layer = label.layer(); + layer.setBorderWidth_(3.0); + + label.setAdjustsFontSizeToFitWidth_(true); + label.setMinimumScaleFactor_(10.0); + label.setClipsToBounds_(true); + if let Some(color) = color { + let background = + UIColor::alloc().initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + ); + label.setTextColor_(background) + } + label }; WidgetNode::new( - Rc::new(RefCell::new(label_builder)), + label.0, self.get_widget_type(), self.get_my_hash() ) diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index bcfcda1312..ace6b67f89 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -181,11 +181,11 @@ where } fn build_uiview(&self, is_root: bool) -> WidgetNode { - let textview_builder = move || unsafe { + let mut ids_to_drop : Vec = Vec::new(); + let textview = unsafe { let ui_textview = { // TODO: Use something better than just a rect. let view = UITextView(UITextView::alloc().init()); - debug!("THIS UITEXT VIEW IS A ROOT? {:?}", is_root); if is_root { let screen = UIScreen::mainScreen(); let frame = screen.bounds(); @@ -202,13 +202,13 @@ where UITextViewTextDidChangeNotification, ui_textview.0, ); - ui_textview.0 + ids_to_drop.push(on_change.id); + ui_textview }; WidgetNode::new( - Rc::new(RefCell::new(textview_builder)), - //textview.0, + textview.0, self.get_widget_type(), self.get_my_hash() ) From 1deeff4f77d5240cd509e565cb21caa2a3e53c9c Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sat, 1 Aug 2020 22:10:36 -0700 Subject: [PATCH 27/33] Cleaned up update logic --- ios/Cargo.toml | 2 - ios/examples/ios-example.rs | 10 ++- ios/src/application.rs | 5 +- ios/src/event.rs | 2 +- ios/src/widget.rs | 147 +++++++---------------------------- ios/src/widget/column.rs | 78 ++++++++++++++----- ios/src/widget/text.rs | 50 +++++++++++- ios/src/widget/text_input.rs | 20 ++++- 8 files changed, 162 insertions(+), 152 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index e690a756cf..d5b22fea86 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -23,8 +23,6 @@ cargo-bundle's support for bundling crate examples. [dependencies] twox-hash = "1.5" raw-window-handle = "^0.3" -# winit = "^0.22" -# winit = { path = "../../winit/" } winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring"} objc = "0.2.7" log = "0.4" diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index b021c8de72..5ec23ccfe8 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -53,7 +53,6 @@ impl Sandbox for Simple { } fn update(&mut self, message: Message) { - log::debug!("GOT NEW MESSAGE: {:?}", message); match message { Message::TextUpdated(val) => { self.text = val; @@ -74,7 +73,7 @@ impl Sandbox for Simple { } ).into() } else { - let column = Column::new() + let mut column = Column::new() .push( Text::new(format!("FOO: {}", &self.text)).color(Color::BLACK) ) @@ -112,6 +111,13 @@ impl Sandbox for Simple { ) */ ; + /* + if self.text.len() % 2 == 0 { + column = column.push( + Text::new(format!("TEXT IS LENGTH 2")).color(Color::BLACK) + ) + } + */ column.into() } } diff --git a/ios/src/application.rs b/ios/src/application.rs index 36884130c0..849dd18559 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -193,10 +193,9 @@ pub trait Application: Sized { runtime.spawn(command); runtime.track(subscription); } - let new_tree = app.view().build_uiview(true); - widget_tree.merge(&new_tree, Some(root_view)); + let element = app.view(); - //widget_tree = element.update(widget_tree, Some(root_view)); + element.update(&mut widget_tree, Some(root_view)); //debug!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} diff --git a/ios/src/event.rs b/ios/src/event.rs index d09013fe19..1a46e2ac86 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -50,7 +50,7 @@ impl EventHandler { } obj }; - debug!("NEW EVENTHANDLER WITH WIDGET ID :{:?}", widget_id); + trace!("NEW EVENTHANDLER WITH WIDGET ID :{:?}", widget_id); Self{id: obj, widget_id} } extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 291de45b2f..e90275610f 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -63,12 +63,22 @@ pub enum WidgetType { Space, } +impl WidgetType { + fn is_mergeable(&self, other: &Self) -> bool { + use WidgetType::*; + match (&self, &other) { + (Text(_), Text(_)) + | (Column(_), Column(_)) + | (TextInput, TextInput) => true, + _ => false, + } + } +} + #[derive(Clone)] pub struct WidgetNode { pub(crate) view_id: id, pub(crate) hash: u64, - //pub(crate) uikit_builder: Rc id>>, - //pub (crate) widget_id: u64, // Used for memory collection. related_ids: Vec, @@ -131,7 +141,7 @@ impl WidgetNode { } pub fn drop_from_ui(&self) { - debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); + trace!("DROPPING A WIDGET NODE! {:?}", self.view_id); use uikit_sys::{INSObject, NSObject, UIView_UIViewHierarchy}; if self.view_id != 0 as id { let view = UIView(self.view_id); @@ -166,118 +176,7 @@ impl WidgetNode { } fn is_mergeable(&self, other: &Self) -> bool { - use WidgetType::*; - match (&self.widget_type, &other.widget_type) { - (Text(_), Text(_)) - | (Column(_), Column(_)) - | (TextInput, TextInput) => true, - _ => false, - } - } - - pub fn merge(&mut self, other: &Self, root_view: Option) { - use uikit_sys::{IUIStackView, UIStackView, UIView_UIViewHierarchy}; - - if !self.is_mergeable(other) { - let old_self = self.clone(); - *self = Self { ..other.clone() }; - if let Some(parent) = root_view { - self.draw(parent); - old_self.drop_from_ui(); - } - old_self.drop_from_ui(); - } else { - match (&mut self.widget_type, &other.widget_type) { - ( - WidgetType::Column(ref mut my_children), - WidgetType::Column(other_children), - ) => { - let stackview = UIStackView(self.view_id); - if my_children.len() == other_children.len() { - for i in 0..my_children.len() { - let current_child = my_children.get_mut(i).unwrap(); - let new_child = other_children.get(i).unwrap(); - - if current_child - .borrow() - .is_mergeable(&new_child.borrow()) - { - current_child - .borrow_mut() - .merge(&new_child.borrow(), None); - new_child.borrow().drop_from_ui(); - } else { - unsafe { - stackview.removeArrangedSubview_( - UIView(current_child.borrow().view_id) - ); - current_child.borrow().drop_from_ui(); - stackview.insertArrangedSubview_atIndex_( - UIView(new_child.borrow().view_id), - i.try_into().unwrap(), - ); - } - my_children[i] = new_child.clone(); - } - } - } else { - let stackview = uikit_sys::UIStackView(self.view_id); - for i in my_children.clone() { - unsafe { - stackview - .removeArrangedSubview_(UIView(i.borrow().view_id)) - } - i.borrow().drop_from_ui(); - } - *my_children = other_children.clone(); - for i in my_children { - unsafe { - stackview - .addArrangedSubview_(UIView(i.borrow().view_id)) - } - } - } - } - ( - WidgetType::Text(current_text), - WidgetType::Text(new_text), - ) => { - if current_text != new_text { - debug!("UPDATING TEXT WIDGET TO {:?}", new_text); - use std::ffi::CString; - use uikit_sys::{ - IUITextView, NSString, - NSString_NSStringExtensionMethods, - NSUTF8StringEncoding, UITextView, - }; - let label = UITextView(self.view_id); - unsafe { - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(new_text.as_str()) - .expect("CString::new failed") - .as_ptr() - as *mut std::ffi::c_void, - new_text.len().try_into().unwrap(), - NSUTF8StringEncoding, - ), - ); - label.setText_(text); - } - } - } - ( - WidgetType::TextInput, - WidgetType::TextInput, - ) => { - debug!("Updating text input widgets is not implemented yet"); - //TODO: Add stuff about Text Input - } - (me, you) => { - debug!("Widget's don't match! {:?}, {:?}", me, you); - } - } - } + self.widget_type.is_mergeable(&other.widget_type) } pub fn add_related_id(&mut self, related_id: id) { @@ -292,11 +191,21 @@ impl WidgetNode { } } e => { - unimplemented!("CHILDREN ARE NOT IMPLEMENTED FOR {:?}", e); + unimplemented!("REPLACE CHILD IS NOT IMPLEMENTED FOR {:?}", e); } } } + pub fn drop_children(&mut self) { + match &mut self.widget_type { + WidgetType::Column(ref mut children) => { + *children = Vec::new(); + } + e => { + unimplemented!("DROP CHILDREN ARE NOT IMPLEMENTED FOR {:?}", e); + } + } + } pub fn add_child(&mut self, child: WidgetNode) { match &mut self.widget_type { @@ -327,8 +236,8 @@ pub trait Widget { ); } - fn update(&self, current_node: WidgetNode, root_view: Option) -> WidgetNode { - unimplemented!( + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + error!( "{:?} using base implementation", self.get_widget_type() ); @@ -400,7 +309,7 @@ impl<'a, Message> Widget for Element<'a, Message> { self.widget.hash_layout(state); } - fn update(&self, current_node: WidgetNode, root_view: Option) -> WidgetNode { + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { self.widget.update(current_node, root_view) } diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 4729356459..6c16656cff 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -6,6 +6,7 @@ use crate::{ use std::cell::RefCell; use std::hash::Hash; use std::rc::Rc; +use std::convert::TryInto; use std::u32; use uikit_sys::{ @@ -144,7 +145,7 @@ where messages: &mut Vec, widget_node: &WidgetNode, ) { - debug!( + trace!( "on_widget_event for column for {:?} children", self.children.len() ); @@ -184,30 +185,67 @@ where fn height(&self) -> Length { self.height } - /* - fn update(&self, mut current_node: WidgetNode, root_view: Option) -> WidgetNode { - /* - match current_node.widget_type { - WidgetType::Column(ref other_children) => { - //if self.children.len() == other_children.len() { - for (i, (child, node)) in - self.children.iter().zip(other_children).enumerate() - { - current_node.replace_child(child.update((*node).into_inner(), None), i); - //node = &Rc::new(RefCell::new(child.update(node.into_inner(), None))); + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + + let mut replace_children = false; + match &mut current_node.widget_type { + WidgetType::Column(ref current_children) => { + if self.children.len() == current_children.len() { + let stackview = UIStackView(current_node.view_id); + for i in 0..self.children.len() { + + let current_child = current_children.get(i).unwrap(); + let old_id = current_child.borrow().view_id; + let element_child = self.children.get(i).unwrap(); + if element_child.get_widget_type().is_mergeable(¤t_child.borrow().widget_type) { + element_child.update(&mut current_child.borrow_mut(), None); + } + if old_id != current_child.borrow().view_id { + unsafe { + stackview.removeArrangedSubview_( + UIView(old_id) + ); + stackview.insertArrangedSubview_atIndex_( + UIView(current_child.borrow().view_id), + i.try_into().unwrap(), + ); + } + } } - current_node - }, + } else { + replace_children = true; + let stackview = uikit_sys::UIStackView(current_node.view_id); + for i in current_children { + unsafe { + stackview + .removeArrangedSubview_(UIView(i.borrow().view_id)) + } + i.borrow().drop_from_ui(); + } + } + } other => { - debug!("current node outdate! {:?}", other); - self.build_uiview(root_view.is_some()) + let new_node = self.build_uiview(root_view.is_some()); + if let Some(root_view) = root_view { + new_node.draw(root_view);; + } + current_node.drop_from_ui(); + *current_node = new_node; + }, + } + if replace_children { + current_node.drop_children(); + for i in &self.children { + let subview = i.build_uiview(false); + let stackview = uikit_sys::UIStackView(current_node.view_id); + unsafe { + stackview + .addArrangedSubview_(UIView(subview.view_id)) + } + current_node.add_child(subview); } } - */ - self.build_uiview(root_view.is_some()) - } -*/ fn get_widget_type(&self) -> WidgetType { WidgetType::Column(Vec::new()) diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index fb9632c592..0ea70525e2 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -148,9 +148,49 @@ impl Widget for Text { fn get_widget_type(&self) -> WidgetType { WidgetType::Text(self.content.clone()) } + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + let mut ids_to_drop : Vec = Vec::new(); + match &mut current_node.widget_type { + WidgetType::Text(ref mut old_text) => { + let new_text = &self.content; + if old_text != new_text { + use std::ffi::CString; + use uikit_sys::{ + IUITextView, NSString, + NSString_NSStringExtensionMethods, + NSUTF8StringEncoding, UITextView, + }; + let label = UITextView(current_node.view_id); + unsafe { + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(new_text.as_str()) + .expect("CString::new failed") + .as_ptr() + as *mut std::ffi::c_void, + new_text.len().try_into().unwrap(), + NSUTF8StringEncoding, + ), + ); + label.setText_(text); + ids_to_drop.push(text.0); + } + } + *old_text = new_text.clone(); + }, + other => { + current_node.drop_from_ui(); + *current_node = self.build_uiview(false); + } + } + for i in &ids_to_drop { + current_node.add_related_id(*i); + } + } fn build_uiview(&self, is_root: bool) -> WidgetNode { let content = self.content.clone(); let color = self.color.clone(); + let mut ids_to_drop : Vec = Vec::new(); let label = unsafe { let label = UILabel::alloc(); label.init(); @@ -164,6 +204,7 @@ impl Widget for Text { uikit_sys::NSUTF8StringEncoding, ), ); + ids_to_drop.push(text.0); label.setText_(text); if is_root { let screen = UIScreen::mainScreen(); @@ -185,15 +226,20 @@ impl Widget for Text { color.b.into(), color.a.into(), ); + ids_to_drop.push(background.0); label.setTextColor_(background) } label }; - WidgetNode::new( + let mut node = WidgetNode::new( label.0, self.get_widget_type(), self.get_my_hash() - ) + ); + for i in &ids_to_drop { + node.add_related_id(*i); + } + node } fn width(&self) -> Length { self.width diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index ace6b67f89..177a4b77f0 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -179,6 +179,16 @@ where fn get_widget_type(&self) -> WidgetType { WidgetType::TextInput } + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + match ¤t_node.widget_type { + WidgetType::TextInput => { + }, + other => { + debug!("Updating from {:?}, to {:?}", other, self.get_widget_type()); + current_node.drop_from_ui(); + } + } + } fn build_uiview(&self, is_root: bool) -> WidgetNode { let mut ids_to_drop : Vec = Vec::new(); @@ -207,11 +217,15 @@ where }; - WidgetNode::new( + let mut node = WidgetNode::new( textview.0, self.get_widget_type(), self.get_my_hash() - ) + ); + for i in &ids_to_drop { + node.add_related_id(*i); + } + node } @@ -221,7 +235,7 @@ where messages: &mut Vec, widget_node: &WidgetNode, ) { - debug!( + trace!( "on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, widget_node.view_id {:?}", widget_event.id, widget_event.widget_id, From 3fba03d4b2f893371a7cdc5a220c444c0287eeb0 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sun, 2 Aug 2020 00:18:52 -0700 Subject: [PATCH 28/33] fixed uikit-sys binding --- ios/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index d5b22fea86..9c75e8cf16 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -27,8 +27,8 @@ winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring objc = "0.2.7" log = "0.4" num-traits = "0.2" -uikit-sys = { path = "../../uikit-sys/" } -#uikit-sys = { git = "https://github.com/simlay/uikit-sys" } +#uikit-sys = { path = "../../uikit-sys/" } +uikit-sys = { git = "https://github.com/simlay/uikit-sys", branch = "bindgen-categories-and-better-return-types" } [dependencies.iced_core] version = "0.2" From eecb8009ab6c701ae837ab54036650ea5dabc963 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Sun, 2 Aug 2020 13:08:05 -0700 Subject: [PATCH 29/33] removed commented code --- ios/src/event.rs | 3 +-- ios/src/widget.rs | 41 +---------------------------------------- ios/src/widget/text.rs | 1 + 3 files changed, 3 insertions(+), 42 deletions(-) diff --git a/ios/src/event.rs b/ios/src/event.rs index 1a46e2ac86..af106571dc 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -44,10 +44,9 @@ impl EventHandler { counter += 1; COUNTER = Some(counter); widget_id = counter; - //widget_id = counter.fetch_add(0, Ordering::SeqCst); (*obj).set_ivar::("widget_id", widget_id); - (*obj).set_ivar::("objc_id", objc_id); } + (*obj).set_ivar::("objc_id", objc_id); obj }; trace!("NEW EVENTHANDLER WITH WIDGET ID :{:?}", widget_id); diff --git a/ios/src/widget.rs b/ios/src/widget.rs index e90275610f..e9f63f19c7 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -255,14 +255,10 @@ pub trait Widget { fn on_widget_event( &mut self, _event: WidgetEvent, - //_layout: Layout<'_>, - //_cursor_position: Point, _messages: &mut Vec, _widget_node: &WidgetNode, - //_renderer: &Renderer, - //_clipboard: Option<&dyn Clipboard>, ) { - //debug!("on_widget_event for {:?}", self.get_widget_type()); + trace!("on_widget_event for {:?}", self.get_widget_type()); } fn width(&self) -> Length; @@ -270,25 +266,12 @@ pub trait Widget { fn height(&self) -> Length; } -//pub type Element<'a, Message> = ElementTemplate + 'a>>; #[allow(missing_debug_implementations)] pub struct Element<'a, Message> { pub widget: Box + 'a>, } -/* -impl> Into for T { - fn into(self) -> WidgetNode { - let mut node = WidgetNode::new(None, self.get_widget_type()); - for i in &self.get_element_children() { - node.add_child(i.widget.into()); - } - node - } -} -*/ - impl<'a, Message> Element<'a, Message> { /// Create a new [`Element`] containing the given [`Widget`]. /// @@ -333,31 +316,9 @@ impl<'a, Message> Widget for Element<'a, Message> { fn on_widget_event( &mut self, event: WidgetEvent, - //_layout: Layout<'_>, - //_cursor_position: Point, messages: &mut Vec, widget_node: &WidgetNode, - //_renderer: &Renderer, - //_clipboard: Option<&dyn Clipboard>, ) { self.widget.on_widget_event(event, messages, widget_node); } } - -/* -impl<'a, Message> From> for WidgetNode { - fn from(element: Element<'a, Message>) -> WidgetNode { - Widget::from(element.widget) - } -} -impl<'a, Message> From + 'a>> for WidgetNode { - fn from(element: Box + 'a>) -> WidgetNode { - Widget::from(element) - } -} -impl<'a, Message> From<&Element<'a, Message>> for WidgetNode { - fn from(element: &Element<'a, Message>) -> WidgetNode { - Widget::from(element.widget) - } -} -*/ diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 0ea70525e2..fa73517549 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -218,6 +218,7 @@ impl Widget for Text { label.setAdjustsFontSizeToFitWidth_(true); label.setMinimumScaleFactor_(10.0); label.setClipsToBounds_(true); + if let Some(color) = color { let background = UIColor::alloc().initWithRed_green_blue_alpha_( From c809263df29cfb2f54ac79847c8dcd159f66b0cf Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Tue, 4 Aug 2020 14:39:12 -0700 Subject: [PATCH 30/33] Added button widget --- ios/Cargo.toml | 1 - ios/examples/ios-example.rs | 41 ++++--------- ios/src/application.rs | 6 +- ios/src/widget.rs | 20 +++---- ios/src/widget/button.rs | 113 +++++++++++++++++++++++++++++------ ios/src/widget/column.rs | 29 ++++----- ios/src/widget/text.rs | 23 +++---- ios/src/widget/text_input.rs | 8 ++- src/lib.rs | 2 +- 9 files changed, 144 insertions(+), 99 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index 9c75e8cf16..d8184e21c7 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -27,7 +27,6 @@ winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring objc = "0.2.7" log = "0.4" num-traits = "0.2" -#uikit-sys = { path = "../../uikit-sys/" } uikit-sys = { git = "https://github.com/simlay/uikit-sys", branch = "bindgen-categories-and-better-return-types" } [dependencies.iced_core] diff --git a/ios/examples/ios-example.rs b/ios/examples/ios-example.rs index 5ec23ccfe8..364e4dfc95 100644 --- a/ios/examples/ios-example.rs +++ b/ios/examples/ios-example.rs @@ -2,19 +2,14 @@ use iced::{ //button, scrollable, slider, text_input, Button, Checkbox, Color, Column, - //Container, Element, HorizontalAlignment, Image, Length, Radio, Row, - //Sandbox, //Scrollable, Settings, Slider, Space, Text, TextInput, Sandbox, Settings, - Command, - executor, Element, Text, TextInput, + Button, text_input, - //Container, Color, - //Checkbox, }; pub fn main() { color_backtrace::install(); @@ -30,15 +25,17 @@ pub fn main() { pub struct Simple { toggle: bool, text: String, + button_press_count: usize, + button_state: iced::button::State, text_state: text_input::State, text_state2: text_input::State, } #[derive(Debug, Clone)] pub enum Message { - //EventOccurred(iced_native::Event), Toggled(bool), TextUpdated(String), TextSubmit, + ButtonPress, } impl Sandbox for Simple { @@ -57,6 +54,9 @@ impl Sandbox for Simple { Message::TextUpdated(val) => { self.text = val; }, + Message::ButtonPress => { + self.button_press_count += 1; + }, _ => { }, } @@ -75,7 +75,12 @@ impl Sandbox for Simple { } else { let mut column = Column::new() .push( - Text::new(format!("FOO: {}", &self.text)).color(Color::BLACK) + Text::new(format!("text box: \"{}\", button pressed {:?}", &self.text, self.button_press_count)).color(Color::BLACK) + ) + .push( + Button::new(&mut self.button_state, + Text::new(format!("FOO: {}", &self.text)).color(Color::BLACK) + ).on_press(Message::ButtonPress) ) .push( TextInput::new( @@ -90,26 +95,6 @@ impl Sandbox for Simple { .push( Text::new(format!("BAR: {}", &self.text)).color(Color::BLACK) ) - /* - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - .push( - Text::new(format!("BAZ: {}", &self.text)).color(Color::BLACK) - ) - */ ; /* if self.text.len() % 2 == 0 { diff --git a/ios/src/application.rs b/ios/src/application.rs index 849dd18559..3a0ea19c0c 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -179,7 +179,7 @@ pub trait Application: Sized { &mut messages, &widget_tree, ); - //debug!("Root widget before: {:?}", widget_tree); + trace!("Root widget before: {:?}", widget_tree); } debug!("NEW MESSAGES! {:?}", messages); for message in messages { @@ -196,7 +196,7 @@ pub trait Application: Sized { let element = app.view(); element.update(&mut widget_tree, Some(root_view)); - //debug!("Root widget after: {:#?}", widget_tree); + trace!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} event::Event::WindowEvent { @@ -204,7 +204,7 @@ pub trait Application: Sized { .. } => {} event::Event::NewEvents(event::StartCause::Init) => { - debug!("WINDOW INIT!"); + trace!("WINDOW INIT!"); let element = app.view(); widget_tree = element.build_uiview(true); let root_view: UIView = UIView(window.ui_view() as id); diff --git a/ios/src/widget.rs b/ios/src/widget.rs index e9f63f19c7..67c2335ea6 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,11 +1,8 @@ use crate::{event::WidgetEvent, Hasher, Length}; -use std::cell::RefCell; -use std::rc::Rc; use uikit_sys::{id, UIView}; use std::convert::TryInto; /* -pub mod button; pub mod checkbox; pub mod container; pub mod image; @@ -17,13 +14,14 @@ mod row; mod space; */ pub mod text_input; +pub mod button; mod column; mod text; -/* #[doc(no_inline)] pub use button::Button; +/* #[doc(no_inline)] pub use scrollable::Scrollable; #[doc(no_inline)] @@ -54,7 +52,7 @@ pub enum WidgetType { Text(String), TextInput, Checkbox, - Column(Vec>>), + Column(Vec>), Container, Image, ProgressBar, @@ -159,7 +157,7 @@ impl WidgetNode { match &self.widget_type { WidgetType::Column(ref children) => { for i in children { - i.borrow().drop_from_ui(); + i.drop_from_ui(); } } _ => {} @@ -175,10 +173,6 @@ impl WidgetNode { } } - fn is_mergeable(&self, other: &Self) -> bool { - self.widget_type.is_mergeable(&other.widget_type) - } - pub fn add_related_id(&mut self, related_id: id) { self.related_ids.push(related_id); } @@ -187,7 +181,7 @@ impl WidgetNode { match &mut self.widget_type { WidgetType::Column(ref mut children) => { if i < children.len() { - children[i] = Rc::new(RefCell::new(child)); + children[i] = Box::new(child); } } e => { @@ -210,7 +204,7 @@ impl WidgetNode { pub fn add_child(&mut self, child: WidgetNode) { match &mut self.widget_type { WidgetType::Column(ref mut children) => { - children.push(Rc::new(RefCell::new(child))); + children.push(Box::new(child)); } e => { unimplemented!("CHILDREN ARE NOT IMPLEMENTED FOR {:?}", e); @@ -236,7 +230,7 @@ pub trait Widget { ); } - fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + fn update(&self, _current_node: &mut WidgetNode, _root_view: Option) { error!( "{:?} using base implementation", self.get_widget_type() diff --git a/ios/src/widget/button.rs b/ios/src/widget/button.rs index b42156c255..13b2d9adb5 100644 --- a/ios/src/widget/button.rs +++ b/ios/src/widget/button.rs @@ -4,12 +4,15 @@ //! //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html -use crate::{Background, Element, Length, Widget, Hasher, layout, Point}; +use crate::{ + widget::{WidgetNode, WidgetType}, + event::{EventHandler, WidgetEvent}, + Background, Element, Hasher, Length, Point, Widget, +}; use std::hash::Hash; pub use iced_style::button::{Style, StyleSheet}; - /// A generic widget that produces a message when pressed. /// /// ``` @@ -129,6 +132,25 @@ impl State { } } +use uikit_sys::{ + UIButton, + IUIButton, + UIScreen, + IUIScreen, + UIButtonType_UIButtonTypePlain, + UIView_UIViewGeometry, + ICALayer, + IUIView, + UIView, + NSString, + NSString_NSStringExtensionMethods, + NSUTF8StringEncoding, + IUIControl, + id, +}; +use std::ffi::CString; +use std::convert::TryInto; + impl<'a, Message> Widget for Button<'a, Message> where Message: 'static + Clone, @@ -147,27 +169,80 @@ where fn height(&self) -> Length { self.height } + fn get_widget_type(&self) -> WidgetType { + WidgetType::Button + } - fn layout( - &self, - limits: &layout::Limits, - ) -> layout::Node { - let padding = f32::from(self.padding); - let limits = limits - .min_width(self.min_width) - .min_height(self.min_height) - .width(self.width) - .height(self.height) - .pad(padding); - - let mut content = self.content.layout(&limits); - content.move_to(Point::new(padding, padding)); - - let size = limits.resolve(content.size()).pad(padding); + fn update(&self, current_node: &mut WidgetNode, root_view: Option) { + match ¤t_node.widget_type { + WidgetType::Button => { + }, + other => { + debug!("Updating from {:?}, to {:?}", other, self.get_widget_type()); + current_node.drop_from_ui(); + let new_node = self.build_uiview(root_view.is_some()); + if let Some(root_view) = root_view { + new_node.draw(root_view); + } + *current_node = new_node; + + } + } + } - layout::Node::with_children(size, vec![content]) + fn build_uiview(&self, is_root: bool) -> WidgetNode { + let button = unsafe { + let button = UIButton(UIButton::buttonWithType_(UIButtonType_UIButtonTypePlain)); + if is_root { + let screen = UIScreen::mainScreen(); + let frame = screen.bounds(); + button.setFrame_(frame); + } + let text = String::from("THIS IS A BUTTON"); + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + CString::new(text.as_str()) + .expect("CString::new failed") + .as_ptr() + as *mut std::ffi::c_void, + text.len().try_into().unwrap(), + NSUTF8StringEncoding, + ), + ); + button.setTitle_forState_(text, uikit_sys::UIControlState_UIControlStateNormal); + let layer = button.layer(); + layer.setBorderWidth_(3.0); + + let on_change = EventHandler::new(button.0); + button.addTarget_action_forControlEvents_( + on_change.id, + sel!(sendEvent), + uikit_sys::UIControlEvents_UIControlEventTouchDown, + ); + button + }; + WidgetNode::new(button.0, WidgetType::Button, self.get_my_hash()) + } + fn on_widget_event( + &mut self, + widget_event: WidgetEvent, + messages: &mut Vec, + widget_node: &WidgetNode, + ) { + debug!( + "BUTTON on_widget_event for text input: widget_event.id: {:x} for widget_id: {:?}, widget_node.view_id {:?}", + widget_event.id, + widget_event.widget_id, + widget_node.view_id, + ); + if widget_event.id as id == widget_node.view_id { + if let Some(message) = self.on_press.clone() { + messages.push(message); + } + } } } + impl<'a, Message> From> for Element<'a, Message> where Message: 'static + Clone, diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 6c16656cff..cfca412b25 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -3,14 +3,12 @@ use crate::{ widget::{WidgetNode, WidgetType}, Align, Element, Hasher, Length, Widget, }; -use std::cell::RefCell; use std::hash::Hash; -use std::rc::Rc; use std::convert::TryInto; use std::u32; use uikit_sys::{ - id, CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, + id, //CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, INSObject, IUIColor, IUIStackView, IUITextView, NSLayoutConstraint, NSLayoutDimension, UIColor, UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, @@ -19,8 +17,6 @@ use uikit_sys::{ UIView_UIViewGeometry, UIView_UIViewHierarchy, UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, UIScreen, - IUIView, - ICALayer, IUIScreen, }; @@ -154,7 +150,7 @@ where for (i, node) in &mut self.children.iter_mut().zip(node_children) { - i.on_widget_event(event.clone(), messages, &node.borrow()); + i.on_widget_event(event.clone(), messages, &node); } } e => { @@ -189,24 +185,24 @@ where let mut replace_children = false; match &mut current_node.widget_type { - WidgetType::Column(ref current_children) => { + WidgetType::Column(ref mut current_children) => { if self.children.len() == current_children.len() { let stackview = UIStackView(current_node.view_id); for i in 0..self.children.len() { - let current_child = current_children.get(i).unwrap(); - let old_id = current_child.borrow().view_id; + let mut current_child = current_children.get_mut(i).unwrap(); + let old_id = current_child.view_id; let element_child = self.children.get(i).unwrap(); - if element_child.get_widget_type().is_mergeable(¤t_child.borrow().widget_type) { - element_child.update(&mut current_child.borrow_mut(), None); + if element_child.get_widget_type().is_mergeable(¤t_child.widget_type) { + element_child.update(&mut current_child, None); } - if old_id != current_child.borrow().view_id { + if old_id != current_child.view_id { unsafe { stackview.removeArrangedSubview_( UIView(old_id) ); stackview.insertArrangedSubview_atIndex_( - UIView(current_child.borrow().view_id), + UIView(current_child.view_id), i.try_into().unwrap(), ); } @@ -218,16 +214,17 @@ where for i in current_children { unsafe { stackview - .removeArrangedSubview_(UIView(i.borrow().view_id)) + .removeArrangedSubview_(UIView(i.view_id)); } - i.borrow().drop_from_ui(); + i.drop_from_ui(); } } } other => { + debug!("{:?} is not a column widget! Dropping!", other); let new_node = self.build_uiview(root_view.is_some()); if let Some(root_view) = root_view { - new_node.draw(root_view);; + new_node.draw(root_view); } current_node.drop_from_ui(); *current_node = new_node; diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index fa73517549..7390e5809f 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -1,7 +1,6 @@ use std::hash::Hash; use crate::{ Color, - // css, Bus, Color, Css, Element, Font, HorizontalAlignment, @@ -15,17 +14,15 @@ use std::convert::TryInto; use std::ffi::CString; use uikit_sys::{ id, - CGPoint, CGRect, CGSize, INSObject, IUIColor, IUILabel, + INSObject, IUIColor, IUILabel, NSString, NSString_NSStringExtensionMethods, UIColor, UILabel, UIView, IUIView, - UIView_UIViewGeometry, UIView_UIViewHierarchy, + UIView_UIViewGeometry, UIView_UIViewRendering, ICALayer, UIScreen, IUIScreen, -}; -use std::{ - cell::RefCell, - rc::Rc, + IUITextView, + NSUTF8StringEncoding, UITextView, }; use std::marker::PhantomData; @@ -154,12 +151,6 @@ impl Widget for Text { WidgetType::Text(ref mut old_text) => { let new_text = &self.content; if old_text != new_text { - use std::ffi::CString; - use uikit_sys::{ - IUITextView, NSString, - NSString_NSStringExtensionMethods, - NSUTF8StringEncoding, UITextView, - }; let label = UITextView(current_node.view_id); unsafe { let text = NSString( @@ -179,8 +170,9 @@ impl Widget for Text { *old_text = new_text.clone(); }, other => { + trace!("{:?} is not a text widget! Dropping!", other); current_node.drop_from_ui(); - *current_node = self.build_uiview(false); + *current_node = self.build_uiview(root_view.is_some()); } } for i in &ids_to_drop { @@ -201,7 +193,7 @@ impl Widget for Text { .expect("CString::new failed") .as_ptr() as *mut std::ffi::c_void, content.len().try_into().unwrap(), - uikit_sys::NSUTF8StringEncoding, + NSUTF8StringEncoding, ), ); ids_to_drop.push(text.0); @@ -211,7 +203,6 @@ impl Widget for Text { let frame = screen.bounds(); label.setFrame_(frame); } - let layer = label.layer(); layer.setBorderWidth_(3.0); diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 177a4b77f0..f926512907 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -16,7 +16,6 @@ use crate::{ pub use iced_style::text_input::{Style, StyleSheet}; use std::{ - cell::RefCell, rc::Rc, u32 }; @@ -186,6 +185,12 @@ where other => { debug!("Updating from {:?}, to {:?}", other, self.get_widget_type()); current_node.drop_from_ui(); + let new_node = self.build_uiview(root_view.is_some()); + if let Some(root_view) = root_view { + new_node.draw(root_view); + } + *current_node = new_node; + } } } @@ -194,7 +199,6 @@ where let mut ids_to_drop : Vec = Vec::new(); let textview = unsafe { let ui_textview = { - // TODO: Use something better than just a rect. let view = UITextView(UITextView::alloc().init()); if is_root { let screen = UIScreen::mainScreen(); diff --git a/src/lib.rs b/src/lib.rs index c8185c49fc..fddc5c6077 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,7 +176,7 @@ //#![deny(missing_docs)] //#![deny(missing_debug_implementations)] //#![deny(unused_results)] -//#![forbid(unsafe_code)] +#![deny(unsafe_code)] #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod application; From 285b589debeacea2129d74751888b8c4025e121c Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Fri, 14 Aug 2020 00:14:01 -0700 Subject: [PATCH 31/33] Not sure --- ios/src/application.rs | 25 ------------------------- ios/src/widget.rs | 1 - 2 files changed, 26 deletions(-) diff --git a/ios/src/application.rs b/ios/src/application.rs index 3a0ea19c0c..391bbdade1 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -131,43 +131,18 @@ pub trait Application: Sized { let window = { let mut window_builder = WindowBuilder::new(); - //let (width, height) = settings.window.size - //let window = Window::new(&event_loop).unwrap(); window_builder = window_builder .with_title(title) .with_decorations(true) - //.with_fullscreen()//Some(Fullscreen::Borderless(window.current_monitor()))) - //.with_inner_size(winit::dpi::LogicalSize { width: 1000, height: 1000}) ; - /* - .with_resizable(settings.window.resizable) - .with_decorations(settings.window.decorations); - */ window_builder.build(&event_loop).expect("Open window") }; let root_view: UIView = UIView(window.ui_view() as id); - unsafe { - let background = UIColor::greenColor(); - //let background = UIColor(UIColor::whiteColor()); - root_view.setBackgroundColor_(background); - /* - let rect = CGRect { - origin: CGPoint { x: 0.0, y: 0.0 }, - size: CGSize { - height: 400.0, - width: 300.0, - }, - }; - root_view.setFrame_(rect); - */ - } let mut widget_tree: WidgetNode = WidgetNode::new(0 as id, crate::widget::WidgetType::BaseElement, 0); event_loop.run( move |event: winit::event::Event, _, control_flow| { - //let new_title = application.borrow().title(); - //debug!("NEW EVENT: {:?}", event); let mut messages: Vec = Vec::new(); match event { event::Event::MainEventsCleared => {} diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 67c2335ea6..104469bf85 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -1,6 +1,5 @@ use crate::{event::WidgetEvent, Hasher, Length}; use uikit_sys::{id, UIView}; -use std::convert::TryInto; /* pub mod checkbox; From cdaf63e0857845d88b1ed32368c595b39f94c59d Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Tue, 8 Sep 2020 14:17:41 -0700 Subject: [PATCH 32/33] Updates for new bindgen features and to fix the merge --- ios/Cargo.toml | 20 +------ ios/examples/icon32x32.png | Bin 2601 -> 0 bytes ios/examples/ios-example.rs | 109 ----------------------------------- ios/src/application.rs | 5 +- ios/src/widget.rs | 17 +++--- ios/src/widget/column.rs | 64 +++++++++++--------- ios/src/widget/text.rs | 97 +++++++++++++++---------------- ios/src/widget/text_input.rs | 2 +- src/error.rs | 2 +- 9 files changed, 97 insertions(+), 219 deletions(-) delete mode 100644 ios/examples/icon32x32.png delete mode 100644 ios/examples/ios-example.rs diff --git a/ios/Cargo.toml b/ios/Cargo.toml index d8184e21c7..ef1b0ab312 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -5,19 +5,6 @@ authors = ["Sebastian Imlay "] edition = "2018" description = "iOS backend for iced" -[package.metadata.bundle.example.ios-example] -name = "ios-example" -identifier = "com.github.iced.simple" -category = "Utility" -icon = ["examples/icon32x32.png"] -short_description = "An example of a bundled application" -long_description = """ -A trivial application that just displays a blank window with -a title bar. It serves as an example of an application that -can be bundled with cargo-bundle, as well as a test-case for -cargo-bundle's support for bundling crate examples. -""" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -27,7 +14,7 @@ winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring objc = "0.2.7" log = "0.4" num-traits = "0.2" -uikit-sys = { git = "https://github.com/simlay/uikit-sys", branch = "bindgen-categories-and-better-return-types" } +uikit-sys = { git = "https://github.com/simlay/uikit-sys", branch = "bindgen-category-and-no-copy" } [dependencies.iced_core] version = "0.2" @@ -41,8 +28,3 @@ path = "../style" version = "0.1" path = "../futures" features = ["thread-pool"] - -[dev-dependencies] -iced = { path = "../", features = ["image", "debug"] } -pretty_env_logger = "0.3" -color-backtrace = { version = "0.4.2", features = ["failure-bt"] } diff --git a/ios/examples/icon32x32.png b/ios/examples/icon32x32.png deleted file mode 100644 index f170e8089b2114feb0f93eb1738aacfe424db5b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2601 zcmY*bcRU+v_m53fwMLDiLCqjmOCkv&6tx?zeeDV&A#nxuioJ@f!)nS)TQwVEwY6KV zR?SkB)-0~wVHCj|z4v#2@B4f{=XuUK&-Z-4=RAKsDK=J@_<-U-006*eW{S3Bo}gpn z=49@~h=@(*!9ujVWC(cDC$Yo~cnGGBL;!$S@Yq-Y`BYIRz!8gaAURlCz`XD{H4ksR zr;i#LM_{4>05}=Oba6f;4-gp_7(|4T5i-9JFs6T;hRA?^K}Z1z83#)nkP$w_2XsMA zTTNXC2?T*a@DOibm>t^W?{a2@kntmt2rvjFEG$edOiK+Pavh=pg+d|fnh;G*RVG4} z7#>9OAgcxuW&a}iKOVFX(JKT?AYt)ApkrPSPkbl|AtQ4v^w0XMPZHMm|CNG>e~-l+ z5ORD2(NI%|{KL&Gg&${O)*)CQrt&dAQUm@A`9Hb8b>NU=@&9V(uStJpnWG|saL7N; zh6Gx?P;dkQxUptv6vlz4{#qmfb4~2153#eZb?8^Ne%(M#a`FILT2;HpX7gY{=4bO9=CGCoJTkwU@h3W6n}! z=ji97+xnmGu$SXm^KV%9rKG>jOvqhR{Q#Fnhcf`HUr|SC8XtE0WcO#@)V4*uA8Qu# zj^zE?IIH64;83cA56UhKa;Znsk45iM(Tr`sF*7&Cy}RDC&qbRbX)*w z%J7;Pypj-P^~bFxuBNVKG#GzeF;B;0L)vx}w<3viK%K{fHUcwB!!2};y+fAsHGF0& z4#4~(q({K#D^H?0o;`X5%{B~Hxsh*oL8Qmm?ZJF+v77aYGrZKuvd{FCn2EJ^Rc@B- zo!*CzlLwPVdX6mhvCjZEZDZTVjKIbe-NWYSe zz22o{*P3%xPF`-B<5#KQgmKhN#e>=;Xt_#Eu4sZTr&H(6TBsCTO`sCw%RID22WBDH zX#b_^!*1E6V0a@KU|&M*?~$PURF3+S(}y>3UoJf4o;4T3Y@S|WI|odi<&gZ%?6Lfh zj&s~eozO;twUUi~-+n+&F=TOmfo3HLG`{qw5PvF%w=pM2y{X5vsiEy`J4#Bl-nhK; z*-T)?{#cr`a@3JdOg6er=UvkHh<;Wc$Hdlhh!1?cH_1!ql!hdaB1Pg}XU^Bv>fqm-v1UDbr-S+>Vd<-*;Rnw@ zyia%V+;V4EG)?8I`{a~23S@MS$t|Yb|60rFW~5xzd(&u4+0qmgz;M+UEQKwj+yHu< zXX5*>rqbqpMI2seD5vjDy($I+Y*XV@q{%NLrQY@wZB|{?Hk1#Id|oV)X+6%8?KbJ8 zBW8T|D8<0ev_Rj{vCSha*iB0SYqx$t?n{DPHGDIs=i@dMpIZc9^h(2SIoQbl-%VPg#;p8 z4nzor?JE`QP>_KXrwNZ%XvhKm}{klB9fdBxL{yg z7$i-94`+Os(-yQ`olpPX#3}oovV+IG6b6-U29Jr6g*q%j?QwJqSJJ28-S;(;BacTm zHaBlP6Cq2fL$|q9 z1v44g;MQkPu8Vdy%<#r$``3(N*xAn;eDf%{hnnuL7IGb)TRyA*{8hDNJ5g$bUHXiz zuLAVXELqv^+3W3gSKK~Vrc_ztm&e(zNGYgVL6R6$xY?lfB0+q8BCk*@D-i*fa%(t` zyEpOj^5R|u$M8k=FMjAp3cMXT%C^{8PDbDEJ3O{9jGug%>T@=*g^ikJirH&e z{C5#3U@ix_iW52T^yCFV9&N^RL6u3R+;VcZ<~mp@_tS z@Ju^!0!22B5)$sO3p4keRSB;9MstZEF7Uy@&D(OaV$U@szqZ14R{U&q`D&) z;@mr>uT;hCxqP3V(9dNo(ff@?U+KhpjNOelM-a)jm?r-I!rd;G4J~A&t|7a5O^%PP ze0g%}84=-CoO}iADSL{a3RTx>h!mi{dNG$b!vLgReyR`6R!iULkqTootdFkPUQe|) zj@iB#=Y&fl<7;&<66D8jUwZ->bWSj)Cu80D9tc$OEQG>m>QTxlHOX1uH714@PHDDi%Kve7I?cOGFygQYCjp zfpTj5>gpfeYca9e)?AJKEI)$zA11_ zuSTxgJRqI|H{F_F+k4q_iSsN?T+=VXq|l`HX2lnY03MOlpQoGoElj|eKQ#8Y=Nja5_3l!H%$4@-2;}Y?C9?W0Ud(G5 z3M${Y!#?)9<9C`1j+U`WeTrOQxVLsSytpMfZz$JF$&8;v1$9H$-~nN)^t)qy{2#3P zSW47|+YvO6;E?&>Tmw-?E7>?rZREI(=#v@|3qm{2=(MWo*3l#)u0-A7DgvuD8 Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Events - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::TextUpdated(val) => { - self.text = val; - }, - Message::ButtonPress => { - self.button_press_count += 1; - }, - _ => { - }, - } - } - - fn view(&mut self) -> Element { - if self.text.starts_with("B") { - TextInput::new( - &mut self.text_state2, - "", - "", - |s| { - Message::TextUpdated(s) - } - ).into() - } else { - let mut column = Column::new() - .push( - Text::new(format!("text box: \"{}\", button pressed {:?}", &self.text, self.button_press_count)).color(Color::BLACK) - ) - .push( - Button::new(&mut self.button_state, - Text::new(format!("FOO: {}", &self.text)).color(Color::BLACK) - ).on_press(Message::ButtonPress) - ) - .push( - TextInput::new( - &mut self.text_state, - "", - "", - |s| { - Message::TextUpdated(s) - } - ) - ) - .push( - Text::new(format!("BAR: {}", &self.text)).color(Color::BLACK) - ) - ; - /* - if self.text.len() % 2 == 0 { - column = column.push( - Text::new(format!("TEXT IS LENGTH 2")).color(Color::BLACK) - ) - } - */ - column.into() - } - } -} diff --git a/ios/src/application.rs b/ios/src/application.rs index 391bbdade1..545c05fe4f 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -139,6 +139,9 @@ pub trait Application: Sized { }; let root_view: UIView = UIView(window.ui_view() as id); + unsafe { + root_view.setBackgroundColor_(UIColor::redColor()); + } let mut widget_tree: WidgetNode = WidgetNode::new(0 as id, crate::widget::WidgetType::BaseElement, 0); event_loop.run( @@ -170,7 +173,7 @@ pub trait Application: Sized { } let element = app.view(); - element.update(&mut widget_tree, Some(root_view)); + element.update(&mut widget_tree, Some(root_view.clone())); trace!("Root widget after: {:#?}", widget_tree); } event::Event::RedrawRequested(_) => {} diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 104469bf85..77a20d1bd9 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -11,26 +11,23 @@ pub mod scrollable; pub mod slider; mod row; mod space; -*/ -pub mod text_input; -pub mod button; - -mod column; -mod text; - -#[doc(no_inline)] -pub use button::Button; -/* #[doc(no_inline)] pub use scrollable::Scrollable; #[doc(no_inline)] pub use slider::Slider; */ + +pub mod button; +#[doc(no_inline)] +pub use button::Button; +mod text; #[doc(no_inline)] pub use text::Text; +pub mod text_input; #[doc(no_inline)] pub use text_input::TextInput; +mod column; pub use column::Column; /* pub use checkbox::Checkbox; diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index cfca412b25..12c68ebdc9 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -3,21 +3,31 @@ use crate::{ widget::{WidgetNode, WidgetType}, Align, Element, Hasher, Length, Widget, }; -use std::hash::Hash; use std::convert::TryInto; +use std::hash::Hash; use std::u32; use uikit_sys::{ id, //CGPoint, CGRect, CGSize, INSLayoutConstraint, INSLayoutDimension, - INSObject, IUIColor, IUIStackView, IUITextView, NSLayoutConstraint, - NSLayoutDimension, UIColor, - UILayoutConstraintAxis_UILayoutConstraintAxisVertical, UIStackView, - UIStackViewAlignment_UIStackViewAlignmentCenter, - UIStackViewDistribution_UIStackViewDistributionFillEqually, UITextView, UIView, - UIView_UIViewGeometry, UIView_UIViewHierarchy, - UIView_UIViewLayoutConstraintCreation, UIView_UIViewRendering, - UIScreen, + INSObject, + IUIColor, IUIScreen, + IUIStackView, + IUITextView, + NSLayoutConstraint, + NSLayoutDimension, + UIColor, + UILayoutConstraintAxis_UILayoutConstraintAxisVertical, + UIScreen, + UIStackView, + UIStackViewAlignment_UIStackViewAlignmentCenter, + UIStackViewDistribution_UIStackViewDistributionFillEqually, + UITextView, + UIView, + UIView_UIViewGeometry, + UIView_UIViewHierarchy, + UIView_UIViewLayoutConstraintCreation, + UIView_UIViewRendering, }; /// A container that distributes its contents vertically. @@ -149,9 +159,9 @@ where WidgetType::Column(node_children) => { for (i, node) in &mut self.children.iter_mut().zip(node_children) - { - i.on_widget_event(event.clone(), messages, &node); - } + { + i.on_widget_event(event.clone(), messages, &node); + } } e => { error!("Widget tree traversal out of sync. {:?} should be a Column!", e); @@ -182,25 +192,26 @@ where self.height } fn update(&self, current_node: &mut WidgetNode, root_view: Option) { - let mut replace_children = false; match &mut current_node.widget_type { WidgetType::Column(ref mut current_children) => { if self.children.len() == current_children.len() { let stackview = UIStackView(current_node.view_id); for i in 0..self.children.len() { - - let mut current_child = current_children.get_mut(i).unwrap(); + let mut current_child = + current_children.get_mut(i).unwrap(); let old_id = current_child.view_id; let element_child = self.children.get(i).unwrap(); - if element_child.get_widget_type().is_mergeable(¤t_child.widget_type) { + if element_child + .get_widget_type() + .is_mergeable(¤t_child.widget_type) + { element_child.update(&mut current_child, None); } if old_id != current_child.view_id { unsafe { - stackview.removeArrangedSubview_( - UIView(old_id) - ); + stackview + .removeArrangedSubview_(UIView(old_id)); stackview.insertArrangedSubview_atIndex_( UIView(current_child.view_id), i.try_into().unwrap(), @@ -210,11 +221,11 @@ where } } else { replace_children = true; - let stackview = uikit_sys::UIStackView(current_node.view_id); + let stackview = + uikit_sys::UIStackView(current_node.view_id); for i in current_children { unsafe { - stackview - .removeArrangedSubview_(UIView(i.view_id)); + stackview.removeArrangedSubview_(UIView(i.view_id)); } i.drop_from_ui(); } @@ -228,7 +239,7 @@ where } current_node.drop_from_ui(); *current_node = new_node; - }, + } } if replace_children { current_node.drop_children(); @@ -236,8 +247,7 @@ where let subview = i.build_uiview(false); let stackview = uikit_sys::UIStackView(current_node.view_id); unsafe { - stackview - .addArrangedSubview_(UIView(subview.view_id)) + stackview.addArrangedSubview_(UIView(subview.view_id)) } current_node.add_child(subview); } @@ -266,7 +276,9 @@ where stack_view.setAxis_( UILayoutConstraintAxis_UILayoutConstraintAxisVertical, ); - stack_view.setAlignment_(uikit_sys::UIStackViewAlignment_UIStackViewAlignmentFill); + stack_view.setAlignment_( + uikit_sys::UIStackViewAlignment_UIStackViewAlignmentFill, + ); stack_view.setDistribution_( UIStackViewDistribution_UIStackViewDistributionFillEqually, ); diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 7390e5809f..8e70e14ba2 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -1,30 +1,18 @@ -use std::hash::Hash; use crate::{ - Color, - Element, - Font, - HorizontalAlignment, - Length, - VerticalAlignment, - Widget, widget::{WidgetNode, WidgetType}, - Hasher, Size, + Color, Element, Font, Hasher, HorizontalAlignment, Length, Size, + VerticalAlignment, Widget, }; use std::convert::TryInto; use std::ffi::CString; +use std::hash::Hash; +use std::marker::PhantomData; use uikit_sys::{ - id, - INSObject, IUIColor, IUILabel, - NSString, NSString_NSStringExtensionMethods, - UIColor, UILabel, UIView, IUIView, - UIView_UIViewGeometry, + id, ICALayer, INSObject, IUIColor, IUILabel, IUIScreen, IUITextView, + IUIView, NSString, NSString_NSStringExtensionMethods, NSUTF8StringEncoding, + UIColor, UILabel, UIScreen, UITextView, UIView, UIView_UIViewGeometry, UIView_UIViewRendering, - ICALayer, - UIScreen, IUIScreen, - IUITextView, - NSUTF8StringEncoding, UITextView, }; -use std::marker::PhantomData; /// A paragraph of text. /// @@ -131,7 +119,6 @@ impl Text { } impl Widget for Text { - fn hash_layout(&self, state: &mut Hasher) { struct Marker; std::any::TypeId::of::().hash(state); @@ -146,7 +133,7 @@ impl Widget for Text { WidgetType::Text(self.content.clone()) } fn update(&self, current_node: &mut WidgetNode, root_view: Option) { - let mut ids_to_drop : Vec = Vec::new(); + let mut ids_to_drop: Vec = Vec::new(); match &mut current_node.widget_type { WidgetType::Text(ref mut old_text) => { let new_text = &self.content; @@ -156,19 +143,19 @@ impl Widget for Text { let text = NSString( NSString::alloc().initWithBytes_length_encoding_( CString::new(new_text.as_str()) - .expect("CString::new failed") - .as_ptr() - as *mut std::ffi::c_void, + .expect("CString::new failed") + .as_ptr() + as *mut std::ffi::c_void, new_text.len().try_into().unwrap(), NSUTF8StringEncoding, ), ); - label.setText_(text); + label.setText_(text.clone()); ids_to_drop.push(text.0); } } *old_text = new_text.clone(); - }, + } other => { trace!("{:?} is not a text widget! Dropping!", other); current_node.drop_from_ui(); @@ -181,22 +168,24 @@ impl Widget for Text { } fn build_uiview(&self, is_root: bool) -> WidgetNode { let content = self.content.clone(); - let color = self.color.clone(); - let mut ids_to_drop : Vec = Vec::new(); + let color = self.color; + let mut ids_to_drop: Vec = Vec::new(); + let cstr = CString::new(content.as_str()) + .expect("CString::new failed") + .as_ptr() as *mut std::ffi::c_void; + + let text = unsafe { + NSString(NSString::alloc().initWithBytes_length_encoding_( + cstr, + content.len().try_into().unwrap(), + NSUTF8StringEncoding, + )) + }; + ids_to_drop.push(text.0); let label = unsafe { let label = UILabel::alloc(); label.init(); - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void, - content.len().try_into().unwrap(), - NSUTF8StringEncoding, - ), - ); - ids_to_drop.push(text.0); label.setText_(text); if is_root { let screen = UIScreen::mainScreen(); @@ -210,23 +199,27 @@ impl Widget for Text { label.setMinimumScaleFactor_(10.0); label.setClipsToBounds_(true); - if let Some(color) = color { - let background = - UIColor::alloc().initWithRed_green_blue_alpha_( - color.r.into(), - color.g.into(), - color.b.into(), - color.a.into(), - ); - ids_to_drop.push(background.0); - label.setTextColor_(background) - } label }; + if let Some(color) = color { + let background = unsafe {UIColor::alloc() + .initWithRed_green_blue_alpha_( + color.r.into(), + color.g.into(), + color.b.into(), + color.a.into(), + ) + }; + ids_to_drop.push(background.0); + unsafe { + label.setTextColor_(background) + } + } + let mut node = WidgetNode::new( label.0, self.get_widget_type(), - self.get_my_hash() + self.get_my_hash(), ); for i in &ids_to_drop { node.add_related_id(*i); @@ -240,11 +233,11 @@ impl Widget for Text { fn height(&self) -> Length { self.height } - } impl<'a, Message> From> for Element<'a, Message> -where Message: 'a +where + Message: 'a, { fn from(text: Text) -> Element<'a, Message> { Element::new(text) diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index f926512907..72ec80db0d 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -213,7 +213,7 @@ where center.addObserver_selector_name_object_( on_change.id, sel!(sendEvent), - UITextViewTextDidChangeNotification, + UITextViewTextDidChangeNotification.clone(), ui_textview.0, ); ids_to_drop.push(on_change.id); diff --git a/src/error.rs b/src/error.rs index 31b87d1748..ed988d78dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,7 @@ pub enum Error { GraphicsAdapterNotFound, } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))] impl From for Error { fn from(error: iced_winit::Error) -> Error { match error { From bd51af88ae2252dd9faaf2590a667727ed308bd9 Mon Sep 17 00:00:00 2001 From: Sebastian Imlay Date: Tue, 15 Sep 2020 17:40:23 -0700 Subject: [PATCH 33/33] Minor cleanups for unsafe blocks --- ios/Cargo.toml | 3 +- ios/Makefile | 3 ++ ios/src/application.rs | 1 + ios/src/event.rs | 5 +- ios/src/widget.rs | 25 +++++----- ios/src/widget/button.rs | 13 +++-- ios/src/widget/column.rs | 94 ++++++++++++++++++------------------ ios/src/widget/text.rs | 57 +++++++++++----------- ios/src/widget/text_input.rs | 88 +++++++++++++++------------------ 9 files changed, 147 insertions(+), 142 deletions(-) diff --git a/ios/Cargo.toml b/ios/Cargo.toml index ef1b0ab312..fb73e30d68 100644 --- a/ios/Cargo.toml +++ b/ios/Cargo.toml @@ -10,11 +10,12 @@ description = "iOS backend for iced" [dependencies] twox-hash = "1.5" raw-window-handle = "^0.3" -winit = { git = "https://github.com/simlay/winit", branch = "ios-rename-nsstring"} +winit = { git = "https://github.com/rust-windowing/winit", branch = "master"} objc = "0.2.7" log = "0.4" num-traits = "0.2" uikit-sys = { git = "https://github.com/simlay/uikit-sys", branch = "bindgen-category-and-no-copy" } +dispatch = "0.2.0" [dependencies.iced_core] version = "0.2" diff --git a/ios/Makefile b/ios/Makefile index 1e4387d3d2..21c0300646 100644 --- a/ios/Makefile +++ b/ios/Makefile @@ -13,3 +13,6 @@ example-run: example-install test: cargo dinghy --platform auto-ios-x86_64 test + +check: + cargo check --target x86_64-apple-ios diff --git a/ios/src/application.rs b/ios/src/application.rs index 545c05fe4f..dfd2865c01 100644 --- a/ios/src/application.rs +++ b/ios/src/application.rs @@ -139,6 +139,7 @@ pub trait Application: Sized { }; let root_view: UIView = UIView(window.ui_view() as id); + // TODO: Make this a debug feature unsafe { root_view.setBackgroundColor_(UIColor::redColor()); } diff --git a/ios/src/event.rs b/ios/src/event.rs index af106571dc..1bdf5dac76 100644 --- a/ios/src/event.rs +++ b/ios/src/event.rs @@ -9,7 +9,7 @@ use objc::{ }; use uikit_sys::id; -use std::sync::atomic::{AtomicU64, Ordering}; + #[derive(PartialEq, Clone, Debug)] @@ -36,6 +36,7 @@ impl EventHandler { pub fn new(objc_id: id) -> Self { let mut widget_id = 0; + // TODO: Figure out how to make this unsafe block much smaller. let obj = unsafe { let obj: id = objc::msg_send![Self::class(), alloc]; let obj: id = objc::msg_send![obj, init]; @@ -54,6 +55,8 @@ impl EventHandler { } extern "C" fn event(this: &Object, _cmd: objc::runtime::Sel) { + + // TODO: Figure out how to make this unsafe block smaller. unsafe { if let Some(ref proxy) = PROXY { let widget_id = *this.get_ivar::("widget_id"); diff --git a/ios/src/widget.rs b/ios/src/widget.rs index 77a20d1bd9..21a24318ca 100644 --- a/ios/src/widget.rs +++ b/ios/src/widget.rs @@ -62,8 +62,8 @@ impl WidgetType { use WidgetType::*; match (&self, &other) { (Text(_), Text(_)) - | (Column(_), Column(_)) - | (TextInput, TextInput) => true, + | (Column(_), Column(_)) + | (TextInput, TextInput) => true, _ => false, } } @@ -90,15 +90,15 @@ impl fmt::Debug for WidgetNode { } } - impl PartialEq for WidgetNode { fn eq(&self, other: &Self) -> bool { self.hash == other.hash && self.widget_type == other.widget_type } } - /* + * + *TODO: Make use of Drop. impl Drop for WidgetNode { fn drop(&mut self) { debug!("DROPPING A WIDGET NODE! {:?}", self.view_id); @@ -150,6 +150,8 @@ impl WidgetNode { obj.dealloc(); } } + + //TODO: WidgetType::Row will also has children. match &self.widget_type { WidgetType::Column(ref children) => { for i in children { @@ -184,8 +186,8 @@ impl WidgetNode { unimplemented!("REPLACE CHILD IS NOT IMPLEMENTED FOR {:?}", e); } } - } + pub fn drop_children(&mut self) { match &mut self.widget_type { WidgetType::Column(ref mut children) => { @@ -226,11 +228,12 @@ pub trait Widget { ); } - fn update(&self, _current_node: &mut WidgetNode, _root_view: Option) { - error!( - "{:?} using base implementation", - self.get_widget_type() - ); + fn update( + &self, + _current_node: &mut WidgetNode, + _root_view: Option, + ) { + error!("{:?} using base implementation", self.get_widget_type()); } fn get_my_hash(&self) -> u64 { @@ -256,7 +259,6 @@ pub trait Widget { fn height(&self) -> Length; } - #[allow(missing_debug_implementations)] pub struct Element<'a, Message> { pub widget: Box + 'a>, @@ -286,7 +288,6 @@ impl<'a, Message> Widget for Element<'a, Message> { self.widget.update(current_node, root_view) } - fn width(&self) -> Length { self.widget.width() } diff --git a/ios/src/widget/button.rs b/ios/src/widget/button.rs index 13b2d9adb5..c8b57306c0 100644 --- a/ios/src/widget/button.rs +++ b/ios/src/widget/button.rs @@ -191,6 +191,11 @@ where } fn build_uiview(&self, is_root: bool) -> WidgetNode { + let text = String::from("THIS IS A BUTTON"); + let cstr = + CString::new(text.as_str()) + .expect("CString::new failed"); + let cstr = cstr.as_ptr() as *mut std::ffi::c_void; let button = unsafe { let button = UIButton(UIButton::buttonWithType_(UIButtonType_UIButtonTypePlain)); if is_root { @@ -198,18 +203,16 @@ where let frame = screen.bounds(); button.setFrame_(frame); } - let text = String::from("THIS IS A BUTTON"); let text = NSString( NSString::alloc().initWithBytes_length_encoding_( - CString::new(text.as_str()) - .expect("CString::new failed") - .as_ptr() - as *mut std::ffi::c_void, + cstr, text.len().try_into().unwrap(), NSUTF8StringEncoding, ), ); button.setTitle_forState_(text, uikit_sys::UIControlState_UIControlStateNormal); + + // TODO: Make this a debug feature let layer = button.layer(); layer.setBorderWidth_(3.0); diff --git a/ios/src/widget/column.rs b/ios/src/widget/column.rs index 12c68ebdc9..2199acd437 100644 --- a/ios/src/widget/column.rs +++ b/ios/src/widget/column.rs @@ -155,67 +155,45 @@ where "on_widget_event for column for {:?} children", self.children.len() ); - match &widget_node.widget_type { - WidgetType::Column(node_children) => { - for (i, node) in - &mut self.children.iter_mut().zip(node_children) - { - i.on_widget_event(event.clone(), messages, &node); - } - } - e => { - error!("Widget tree traversal out of sync. {:?} should be a Column!", e); + if let WidgetType::Column(node_children) = &widget_node.widget_type { + for (i, node) in &mut self.children.iter_mut().zip(node_children) { + i.on_widget_event(event.clone(), messages, &node); } + } else { + error!( + "Widget tree traversal out of sync. {:?} should be a Column!", + widget_node.widget_type + ); } } - - fn hash_layout(&self, state: &mut Hasher) { - struct Marker; - std::any::TypeId::of::().hash(state); - - self.width.hash(state); - self.height.hash(state); - self.max_width.hash(state); - self.max_height.hash(state); - self.align_items.hash(state); - self.spacing.hash(state); - - for child in &self.children { - child.widget.hash_layout(state); - } - } - fn width(&self) -> Length { - self.width - } - - fn height(&self) -> Length { - self.height - } fn update(&self, current_node: &mut WidgetNode, root_view: Option) { let mut replace_children = false; match &mut current_node.widget_type { WidgetType::Column(ref mut current_children) => { + // If we have the same if self.children.len() == current_children.len() { let stackview = UIStackView(current_node.view_id); for i in 0..self.children.len() { let mut current_child = current_children.get_mut(i).unwrap(); - let old_id = current_child.view_id; let element_child = self.children.get(i).unwrap(); if element_child .get_widget_type() .is_mergeable(¤t_child.widget_type) { + // TODO: Probably should do something smarter than just compare + // pointers. + let old_id = current_child.view_id; element_child.update(&mut current_child, None); - } - if old_id != current_child.view_id { - unsafe { - stackview - .removeArrangedSubview_(UIView(old_id)); - stackview.insertArrangedSubview_atIndex_( - UIView(current_child.view_id), - i.try_into().unwrap(), - ); + if old_id != current_child.view_id { + unsafe { + stackview + .removeArrangedSubview_(UIView(old_id)); + stackview.insertArrangedSubview_atIndex_( + UIView(current_child.view_id), + i.try_into().unwrap(), + ); + } } } } @@ -254,10 +232,6 @@ where } } - fn get_widget_type(&self) -> WidgetType { - WidgetType::Column(Vec::new()) - } - fn build_uiview(&self, is_root: bool) -> WidgetNode { let stackview = unsafe { let stack_view = UIStackView(UIStackView::alloc().init()); @@ -299,6 +273,32 @@ where } stackview_node } + fn get_widget_type(&self) -> WidgetType { + WidgetType::Column(Vec::new()) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + self.align_items.hash(state); + self.spacing.hash(state); + + for child in &self.children { + child.widget.hash_layout(state); + } + } + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } } impl<'a, Message> From> for Element<'a, Message> diff --git a/ios/src/widget/text.rs b/ios/src/widget/text.rs index 8e70e14ba2..683759f3cf 100644 --- a/ios/src/widget/text.rs +++ b/ios/src/widget/text.rs @@ -134,33 +134,34 @@ impl Widget for Text { } fn update(&self, current_node: &mut WidgetNode, root_view: Option) { let mut ids_to_drop: Vec = Vec::new(); - match &mut current_node.widget_type { - WidgetType::Text(ref mut old_text) => { - let new_text = &self.content; - if old_text != new_text { - let label = UITextView(current_node.view_id); - unsafe { - let text = NSString( - NSString::alloc().initWithBytes_length_encoding_( - CString::new(new_text.as_str()) - .expect("CString::new failed") - .as_ptr() - as *mut std::ffi::c_void, - new_text.len().try_into().unwrap(), - NSUTF8StringEncoding, - ), - ); - label.setText_(text.clone()); - ids_to_drop.push(text.0); - } + let new_text = &self.content; + let cstr = CString::new(new_text.as_str()) + .expect("CString::new failed"); + let cstr = cstr.as_ptr() as *mut std::ffi::c_void; + //.as_ptr() + //as *mut std::ffi::c_void; + if let WidgetType::Text(ref mut old_text) = &mut current_node.widget_type { + + // TODO: check/update the styles of the text + if old_text != new_text { + let label = UITextView(current_node.view_id); + unsafe { + let text = NSString( + NSString::alloc().initWithBytes_length_encoding_( + cstr, + new_text.len().try_into().unwrap(), + NSUTF8StringEncoding, + ), + ); + label.setText_(text.clone()); + ids_to_drop.push(text.0); } - *old_text = new_text.clone(); - } - other => { - trace!("{:?} is not a text widget! Dropping!", other); - current_node.drop_from_ui(); - *current_node = self.build_uiview(root_view.is_some()); } + *old_text = new_text.clone(); + } else { + current_node.drop_from_ui(); + trace!("{:?} is not a text widget! Dropping!", current_node.widget_type); + *current_node = self.build_uiview(root_view.is_some()); } for i in &ids_to_drop { current_node.add_related_id(*i); @@ -171,8 +172,8 @@ impl Widget for Text { let color = self.color; let mut ids_to_drop: Vec = Vec::new(); let cstr = CString::new(content.as_str()) - .expect("CString::new failed") - .as_ptr() as *mut std::ffi::c_void; + .expect("CString::new failed"); + let cstr = cstr.as_ptr() as *mut std::ffi::c_void; let text = unsafe { NSString(NSString::alloc().initWithBytes_length_encoding_( @@ -192,6 +193,8 @@ impl Widget for Text { let frame = screen.bounds(); label.setFrame_(frame); } + + // TODO: Make this a debug feature let layer = label.layer(); layer.setBorderWidth_(3.0); diff --git a/ios/src/widget/text_input.rs b/ios/src/widget/text_input.rs index 72ec80db0d..2129f315ee 100644 --- a/ios/src/widget/text_input.rs +++ b/ios/src/widget/text_input.rs @@ -16,41 +16,20 @@ use crate::{ pub use iced_style::text_input::{Style, StyleSheet}; use std::{ + ffi::CStr, rc::Rc, u32 }; -use std::convert::TryInto; use uikit_sys::{ - id, CGPoint, CGRect, CGSize, INSNotificationCenter, INSObject, IUITextView, + id, INSNotificationCenter, INSObject, IUITextView, NSNotificationCenter, NSString, NSString_NSStringExtensionMethods, UITextView, UITextViewTextDidChangeNotification, UIView, IUIView, UIView_UIViewHierarchy, UIView_UIViewGeometry, - CALayer, + ICALayer, UIScreen, IUIScreen, }; - -/// A field that can be filled with text. -/// -/// # Example -/// ``` -/// # use iced_web::{text_input, TextInput}; -/// # -/// enum Message { -/// TextInputChanged(String), -/// } -/// -/// let mut state = text_input::State::new(); -/// let value = "Some text"; -/// -/// let input = TextInput::new( -/// &mut state, -/// "This is the placeholder...", -/// value, -/// Message::TextInputChanged, -/// ); -/// ``` #[allow(missing_debug_implementations)] pub struct TextInput<'a, Message> { _state: &'a mut State, @@ -181,6 +160,7 @@ where fn update(&self, current_node: &mut WidgetNode, root_view: Option) { match ¤t_node.widget_type { WidgetType::TextInput => { + // TODO: check/update the styles of the input box. }, other => { debug!("Updating from {:?}, to {:?}", other, self.get_widget_type()); @@ -196,27 +176,33 @@ where } fn build_uiview(&self, is_root: bool) -> WidgetNode { + info!("Building text input uiview"); let mut ids_to_drop : Vec = Vec::new(); - let textview = unsafe { + let textview = { let ui_textview = { - let view = UITextView(UITextView::alloc().init()); + let view = unsafe {UITextView(UITextView::alloc().init())}; if is_root { - let screen = UIScreen::mainScreen(); - let frame = screen.bounds(); - view.setFrame_(frame); + unsafe { + let frame = UIScreen::mainScreen().bounds(); + view.setFrame_(frame); + } } view }; + let on_change = EventHandler::new(ui_textview.0); // https://developer.apple.com/documentation/foundation/nsnotificationcenter/1415360-addobserver?language=objc - let center = NSNotificationCenter::defaultCenter(); - center.addObserver_selector_name_object_( - on_change.id, - sel!(sendEvent), - UITextViewTextDidChangeNotification.clone(), - ui_textview.0, - ); + unsafe { + NSNotificationCenter::defaultCenter().addObserver_selector_name_object_( + on_change.id, + sel!(sendEvent), + UITextViewTextDidChangeNotification.clone(), + ui_textview.0, + ); + } ids_to_drop.push(on_change.id); + // TODO: Make this a debug feature + unsafe { ui_textview.layer().setBorderWidth_(3.0); } ui_textview }; @@ -232,7 +218,6 @@ where node } - fn on_widget_event( &mut self, widget_event: WidgetEvent, @@ -247,24 +232,29 @@ where ); if widget_event.id as id == widget_node.view_id { let ui_textview = UITextView(widget_event.id as id); - let value = unsafe { - let value = ui_textview.text(); - let len = value - .lengthOfBytesUsingEncoding_(uikit_sys::NSUTF8StringEncoding); - let bytes = value.UTF8String() as *const u8; - String::from_utf8( - std::slice::from_raw_parts(bytes, len.try_into().unwrap()) - .to_vec(), - ) - .unwrap() + let value = { + // This is only unsafe due to the FFI of bindgen. + // This copies the NSString and so we will be taking ownership of it. + let value = unsafe {ui_textview.text()}; + + // The documentation on weather this is nullable is unclear. Best to check anyway. + if value.0 == 0 as id { + return; + } + let bytes = unsafe { value.UTF8String() }; + if bytes.is_null() { + return; + } + let cstr = unsafe { CStr::from_ptr(bytes) }; + cstr.to_string_lossy().to_owned().to_string() }; if value.ends_with("\n") { if let Some(on_submit) = self.on_submit.take() { messages.push(on_submit); } } else { - self.value = value; messages.push((self.on_change)(self.value.clone())); + self.value = value; } } } @@ -274,7 +264,7 @@ where } fn height(&self) -> Length { - todo!() + Length::Shrink } }