diff --git a/Cargo.toml b/Cargo.toml index 60c6fa0..3458ce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_iced" -version = "0.3.1" +version = "0.4.0" edition = "2021" description = "Iced integration for Bevy" authors = ["tasgon "] @@ -12,22 +12,22 @@ documentation = "https://docs.rs/bevy_iced" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -touch = [] - [dependencies] -bevy_app = "0.10" -bevy_derive = "0.10" -bevy_ecs = "0.10" -bevy_input = "0.10" -bevy_math = "0.10" -bevy_render = "0.10" -bevy_utils = "0.10" -bevy_window = "0.10" +bevy_app = "0.11" +bevy_derive = "0.11" +bevy_ecs = "0.11" +bevy_input = "0.11" +bevy_math = "0.11" +bevy_render = "0.11" +bevy_utils = "0.11" +bevy_window = "0.11" -iced_wgpu = "0.10" -iced_native = "0.10" +iced_core = "0.10" +iced_graphics = "0.9" +iced_runtime = "0.1" +iced_wgpu = "0.11" +iced_widget = "0.1" [dev-dependencies] -bevy = "0.10" -rand = "0.8" +bevy = "0.11" +rand = "0.8" \ No newline at end of file diff --git a/README.md b/README.md index 4c7894e..cc5b630 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,15 @@ use bevy::prelude::*; use bevy_iced::iced::widget::text; use bevy_iced::{IcedContext, IcedPlugin}; +#[derive(Event)] pub enum UiMessage {} pub fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(IcedPlugin) + .add_plugins(IcedPlugin::default()) .add_event::() - .add_system(ui_system) + .add_systems(Update, ui_system) .run(); } @@ -35,7 +36,8 @@ See the [examples](https://github.com/tasgon/bevy_iced/tree/master/examples) and |Bevy Version |Crate Version | |--------------|---------------| -|`0.10` |`0.3`, `master`| +|`0.11` |`0.4`, `master`| +|`0.10` |`0.3`, | |`0.9` |`0.2` | |`0.7` |`0.1` | @@ -47,3 +49,4 @@ See the [examples](https://github.com/tasgon/bevy_iced/tree/master/examples) and ## Credits - [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for giving me a useful starting point to do this +- [Joonas Satka](https://github.com/jsatka) for helping me port to Bevy 0.11 diff --git a/assets/fonts/AlphaProta-License.txt b/assets/fonts/AlphaProta-License.txt new file mode 100644 index 0000000..354f1e0 --- /dev/null +++ b/assets/fonts/AlphaProta-License.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/assets/fonts/AlphaProta.ttf b/assets/fonts/AlphaProta.ttf new file mode 100644 index 0000000..e729620 Binary files /dev/null and b/assets/fonts/AlphaProta.ttf differ diff --git a/examples/basic.rs b/examples/basic.rs index 11b2e2f..2601d01 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -2,14 +2,15 @@ use bevy::prelude::*; use bevy_iced::iced::widget::text; use bevy_iced::{IcedContext, IcedPlugin}; +#[derive(Event)] pub enum UiMessage {} pub fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(IcedPlugin) + .add_plugins(IcedPlugin::default()) .add_event::() - .add_system(ui_system) + .add_systems(Update, ui_system) .run(); } diff --git a/examples/fonts.rs b/examples/fonts.rs new file mode 100644 index 0000000..6f7b620 --- /dev/null +++ b/examples/fonts.rs @@ -0,0 +1,36 @@ +use bevy::prelude::*; +use bevy_iced::iced::{ + font, + widget::{column, text}, + Font, +}; +use bevy_iced::{iced, IcedContext, IcedPlugin}; + +const ALPHAPROTA_FONT: Font = Font::with_name("Alpha Prota"); +const ALPHAPROTA_FONT_BYTES: &'static [u8] = include_bytes!("../assets/fonts/AlphaProta.ttf"); + +#[derive(Event)] +pub enum UiMessage {} + +pub fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(IcedPlugin { + fonts: vec![ALPHAPROTA_FONT_BYTES], + settings: iced::Settings { + default_text_size: 40.0, + default_font: ALPHAPROTA_FONT, + ..Default::default() + }, + }) + .add_event::() + .add_systems(Update, ui_system) + .run(); +} + +fn ui_system(mut ctx: IcedContext) { + ctx.display(column!( + text(format!("I am the default font")).font(font::Font::DEFAULT), + text(format!("I am another font")) + )); +} diff --git a/examples/interactive.rs b/examples/interactive.rs index ff39cf8..23b39b9 100644 --- a/examples/interactive.rs +++ b/examples/interactive.rs @@ -3,13 +3,15 @@ use bevy::{ input::mouse::{MouseButtonInput, MouseWheel}, prelude::*, }; -use bevy_iced::{ - iced::widget::{slider, text, text_input, Button, Column, Row}, - IcedContext, IcedPlugin, IcedSettings, +use bevy_iced::iced::{ + self, + widget::{slider, text, text_input, Button, Column, Row}, + Alignment, Style, }; +use bevy_iced::{IcedContext, IcedPlugin, IcedSettings}; use rand::random as rng; -#[derive(Clone)] +#[derive(Clone, Event)] enum UiMessage { BoxRequested, Scale(f32), @@ -34,9 +36,11 @@ pub fn main() { }), ..Default::default() })) - .add_plugin(IcedPlugin) - .add_plugin(FrameTimeDiagnosticsPlugin::default()) - .add_plugin(LogDiagnosticsPlugin::default()) + .add_plugins(( + IcedPlugin::default(), + FrameTimeDiagnosticsPlugin::default(), + LogDiagnosticsPlugin::default(), + )) .add_event::() .insert_resource(UiActive(true)) .insert_resource(UiData { @@ -45,17 +49,17 @@ pub fn main() { }) .insert_resource(IcedSettings { scale_factor: None, - theme: bevy_iced::iced_wgpu::Theme::Light, - style: bevy_iced::iced::renderer::Style { - text_color: bevy_iced::iced::Color::from_rgb(0.0, 1.0, 1.0), + theme: iced::Theme::Light, + style: Style { + text_color: iced::Color::from_rgb(0.0, 1.0, 1.0), }, + ..Default::default() }) - .add_startup_system(build_program) - .add_system(tick) - .add_system(box_system) - .add_system(update_scale_factor) - .add_system(toggle_ui) - .add_system(ui_system) + .add_systems(Startup, build_program) + .add_systems( + Update, + (tick, box_system, update_scale_factor, toggle_ui, ui_system), + ) .run(); } @@ -137,7 +141,7 @@ fn ui_system( let row = Row::new() .spacing(10) - .align_items(iced_native::Alignment::Center) + .align_items(Alignment::Center) .push(Button::new(text("Request box")).on_press(UiMessage::BoxRequested)) .push(text(format!( "{} boxes (amplitude: {})", @@ -146,7 +150,7 @@ fn ui_system( ))); let edit = text_input("", &data.text).on_input(UiMessage::Text); let column = Column::new() - .align_items(iced_native::Alignment::Center) + .align_items(Alignment::Center) .spacing(10) .push(edit) .push(slider(0.0..=100.0, data.scale, UiMessage::Scale)) diff --git a/examples/toggle.rs b/examples/toggle.rs index 2c76e6c..9da170d 100644 --- a/examples/toggle.rs +++ b/examples/toggle.rs @@ -4,6 +4,7 @@ use bevy_iced::{IcedContext, IcedPlugin}; use bevy_input::keyboard::KeyboardInput; use bevy_input::ButtonState; +#[derive(Event)] pub enum UiMessage {} #[derive(Resource, PartialEq, Eq)] @@ -12,11 +13,11 @@ pub struct UiActive(bool); pub fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugin(IcedPlugin) + .add_plugins(IcedPlugin::default()) .add_event::() .insert_resource(UiActive(true)) - .add_system(toggle_system) - .add_system(ui_system.run_if(resource_equals(UiActive(true)))) + .add_systems(Update, toggle_system) + .add_systems(Update, ui_system.run_if(resource_equals(UiActive(true)))) .run(); } diff --git a/src/conversions.rs b/src/conversions.rs index 8727a99..5f2f641 100644 --- a/src/conversions.rs +++ b/src/conversions.rs @@ -1,15 +1,12 @@ +use crate::iced::{ + touch::{self, Finger}, + Point, +}; use bevy_input::prelude::KeyCode as BevyKeyCode; use bevy_input::prelude::MouseButton; -#[cfg(feature = "touch")] use bevy_input::touch::{TouchInput, TouchPhase}; -#[cfg(feature = "touch")] use bevy_math::Vec2; -use iced_native::keyboard::KeyCode as IcedKeyCode; -#[cfg(feature = "touch")] -use iced_native::{ - touch::{self, Finger}, - Point, -}; +use iced_core::keyboard::KeyCode as IcedKeyCode; pub fn key_code(virtual_keycode: BevyKeyCode) -> IcedKeyCode { match virtual_keycode { @@ -123,11 +120,11 @@ pub fn key_code(virtual_keycode: BevyKeyCode) -> IcedKeyCode { BevyKeyCode::Grave => IcedKeyCode::Grave, BevyKeyCode::Kana => IcedKeyCode::Kana, BevyKeyCode::Kanji => IcedKeyCode::Kanji, - BevyKeyCode::LAlt => IcedKeyCode::LAlt, - BevyKeyCode::LBracket => IcedKeyCode::LBracket, - BevyKeyCode::LControl => IcedKeyCode::LControl, - BevyKeyCode::LShift => IcedKeyCode::LShift, - BevyKeyCode::LWin => IcedKeyCode::LWin, + BevyKeyCode::AltLeft => IcedKeyCode::LAlt, + BevyKeyCode::BracketLeft => IcedKeyCode::LBracket, + BevyKeyCode::ControlLeft => IcedKeyCode::LControl, + BevyKeyCode::ShiftLeft => IcedKeyCode::LShift, + BevyKeyCode::SuperLeft => IcedKeyCode::LWin, BevyKeyCode::Mail => IcedKeyCode::Mail, BevyKeyCode::MediaSelect => IcedKeyCode::MediaSelect, BevyKeyCode::MediaStop => IcedKeyCode::MediaStop, @@ -147,11 +144,11 @@ pub fn key_code(virtual_keycode: BevyKeyCode) -> IcedKeyCode { BevyKeyCode::PlayPause => IcedKeyCode::PlayPause, BevyKeyCode::Power => IcedKeyCode::Power, BevyKeyCode::PrevTrack => IcedKeyCode::PrevTrack, - BevyKeyCode::RAlt => IcedKeyCode::RAlt, - BevyKeyCode::RBracket => IcedKeyCode::RBracket, - BevyKeyCode::RControl => IcedKeyCode::RControl, - BevyKeyCode::RShift => IcedKeyCode::RShift, - BevyKeyCode::RWin => IcedKeyCode::RWin, + BevyKeyCode::AltRight => IcedKeyCode::RAlt, + BevyKeyCode::BracketRight => IcedKeyCode::RBracket, + BevyKeyCode::ControlRight => IcedKeyCode::RControl, + BevyKeyCode::ShiftRight => IcedKeyCode::RShift, + BevyKeyCode::SuperRight => IcedKeyCode::RWin, BevyKeyCode::Semicolon => IcedKeyCode::Semicolon, BevyKeyCode::Slash => IcedKeyCode::Slash, BevyKeyCode::Sleep => IcedKeyCode::Sleep, @@ -179,17 +176,16 @@ pub fn key_code(virtual_keycode: BevyKeyCode) -> IcedKeyCode { } } -pub fn mouse_button(button: MouseButton) -> iced_native::mouse::Button { - use iced_native::mouse::Button; +pub fn mouse_button(button: MouseButton) -> iced_core::mouse::Button { + use iced_core::mouse::Button; match button { MouseButton::Left => Button::Left, MouseButton::Right => Button::Right, MouseButton::Middle => Button::Middle, - MouseButton::Other(val) => Button::Other(val as u8), + MouseButton::Other(val) => Button::Other(val), } } -#[cfg(feature = "touch")] pub fn touch_event(bevy_touch_input: &TouchInput) -> touch::Event { match *bevy_touch_input { TouchInput { @@ -202,7 +198,7 @@ pub fn touch_event(bevy_touch_input: &TouchInput) -> touch::Event { position: Point { x, y }, }, TouchInput { - phase: TouchPhase::Cancelled, + phase: TouchPhase::Canceled, position: Vec2 { x, y }, id: finger, .. diff --git a/src/iced.rs b/src/iced.rs new file mode 100644 index 0000000..70b403d --- /dev/null +++ b/src/iced.rs @@ -0,0 +1,84 @@ +// Largely taken from https://github.com/iced-rs/iced/blob/0.10/src/lib.rs + +use iced_widget::renderer; +use iced_widget::style; + +pub use style::theme; + +pub use iced_core::alignment; +pub use iced_core::event; +pub use iced_core::gradient; +pub use iced_core::{ + color, Alignment, Background, BorderRadius, Color, ContentFit, Degrees, Gradient, Length, + Padding, Pixels, Point, Radians, Rectangle, Size, Vector, +}; +pub use iced_runtime::Command; + +pub mod clipboard { + //! Access the clipboard. + pub use iced_runtime::clipboard::{read, write}; +} + +pub mod font { + //! Load and use fonts. + pub use iced_core::font::*; + pub use iced_runtime::font::*; +} + +pub mod keyboard { + //! Listen and react to keyboard events. + pub use iced_core::keyboard::{Event, KeyCode, Modifiers}; +} + +pub mod mouse { + //! Listen and react to mouse events. + pub use iced_core::mouse::{Button, Cursor, Event, Interaction, ScrollDelta}; +} + +pub mod overlay { + //! Display interactive elements on top of other widgets. + + /// A generic [`Overlay`]. + /// + /// This is an alias of an `iced_native` element with a default `Renderer`. + /// + /// [`Overlay`]: iced_native::Overlay + pub type Element<'a, Message, Renderer = crate::Renderer> = + iced_core::overlay::Element<'a, Message, Renderer>; + + pub use iced_widget::overlay::*; +} + +pub mod touch { + //! Listen and react to touch events. + pub use iced_core::touch::{Event, Finger}; +} + +pub mod widget { + //! Use the built-in widgets or create your own. + pub use iced_widget::*; + + // We hide the re-exported modules by `iced_widget` + mod core {} + mod graphics {} + mod native {} + mod renderer {} + mod style {} + mod runtime {} +} + +pub use event::Event; +pub use font::Font; +pub use theme::Theme; + +/// The default renderer. +pub type Renderer = renderer::Renderer; + +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +pub type Element<'a, Message, Renderer = crate::Renderer> = + iced_core::Element<'a, Message, Renderer>; + +pub use iced_core::renderer::Style; +pub use iced_wgpu::Settings; diff --git a/src/lib.rs b/src/lib.rs index f03f846..b340b11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,15 @@ //! use bevy_iced::iced::widget::text; //! use bevy_iced::{IcedContext, IcedPlugin}; //! +//! #[derive(Event)] //! pub enum UiMessage {} //! //! pub fn main() { //! App::new() //! .add_plugins(DefaultPlugins) -//! .add_plugin(IcedPlugin) +//! .add_plugins(IcedPlugin::default()) //! .add_event::() -//! .add_system(ui_system) +//! .add_systems(Update, ui_system) //! .run(); //! } //! @@ -23,102 +24,112 @@ //! ))); //! } //! ``` -//! -//! ## Feature flags -//! -//! - `touch`: Enables touch input. Is not exclude input from the mouse. #![deny(unsafe_code)] #![deny(missing_docs)] use std::any::{Any, TypeId}; +use std::borrow::Cow; use std::sync::Arc; use std::sync::Mutex; -use crate::render::IcedNode; -use crate::render::ViewportResource; +use crate::render::{extract_iced_data, IcedNode, ViewportResource}; -use bevy_app::{App, IntoSystemAppConfig, Plugin}; +use bevy_app::{App, Plugin, Update}; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::event::Event; use bevy_ecs::prelude::{EventWriter, Query, With}; use bevy_ecs::system::{NonSendMut, Res, ResMut, Resource, SystemParam}; -#[cfg(feature = "touch")] use bevy_input::touch::Touches; -use bevy_math::Vec2; use bevy_render::render_graph::RenderGraph; -use bevy_render::renderer::RenderDevice; +use bevy_render::renderer::{RenderDevice, RenderQueue}; use bevy_render::{ExtractSchedule, RenderApp}; use bevy_utils::HashMap; use bevy_window::{PrimaryWindow, Window}; -use iced::{user_interface, Element, UserInterface}; -pub use iced_native as iced; -use iced_native::{Debug, Size}; -pub use iced_wgpu; -use iced_wgpu::{wgpu, Viewport}; +use iced_core::mouse::Cursor; +use iced_graphics::backend::Text; +use iced_graphics::Viewport; +use iced_runtime::user_interface::UserInterface; + +/// Basic re-exports for all Iced-related stuff. +/// +/// This module attempts to emulate the `iced` package's API +/// as much as possible. +pub mod iced; mod conversions; mod render; mod systems; +mod utils; use systems::IcedEventQueue; +/// The default renderer. +pub type Renderer = iced_wgpu::Renderer; + /// The main feature of `bevy_iced`. -/// Add this to your [`App`] by calling `app.add_plugin(bevy_iced::IcedPlugin)`. -pub struct IcedPlugin; +/// Add this to your [`App`] by calling `app.add_plugin(bevy_iced::IcedPlugin::default())`. +#[derive(Default)] +pub struct IcedPlugin { + /// The settings that Iced should use. + pub settings: iced::Settings, + /// Font file contents + pub fonts: Vec<&'static [u8]>, +} impl Plugin for IcedPlugin { fn build(&self, app: &mut App) { - let default_viewport = Viewport::with_physical_size(Size::new(1600, 900), 1.0); - let default_viewport = ViewportResource(default_viewport); - let iced_resource: IcedResource = IcedProps::new(app).into(); - - app.add_system(systems::process_input) - .add_system(render::update_viewport) + app.add_systems(Update, (systems::process_input, render::update_viewport)) .insert_resource(DidDraw::default()) - .insert_resource(iced_resource.clone()) .insert_resource(IcedSettings::default()) .insert_non_send_resource(IcedCache::default()) - .insert_resource(IcedEventQueue::default()) - .insert_resource(default_viewport.clone()); + .insert_resource(IcedEventQueue::default()); + } + + fn finish(&self, app: &mut App) { + let default_viewport = Viewport::with_physical_size(iced_core::Size::new(1600, 900), 1.0); + let default_viewport = ViewportResource(default_viewport); + let iced_resource: IcedResource = IcedProps::new(app, self).into(); + + app.insert_resource(default_viewport.clone()) + .insert_resource(iced_resource.clone()); let render_app = app.sub_app_mut(RenderApp); render_app .insert_resource(default_viewport) .insert_resource(iced_resource) - .add_system(render::extract_iced_data.in_schedule(ExtractSchedule)); + .add_systems(ExtractSchedule, extract_iced_data); setup_pipeline(&mut render_app.world.get_resource_mut().unwrap()); } } struct IcedProps { - renderer: iced_wgpu::Renderer, - debug: iced_native::Debug, - clipboard: iced_native::clipboard::Null, + renderer: Renderer, + debug: iced_runtime::Debug, + clipboard: iced_core::clipboard::Null, } impl IcedProps { - fn new(app: &App) -> Self { - let device = app - .sub_app(RenderApp) - .world + fn new(app: &App, config: &IcedPlugin) -> Self { + let render_world = &app.sub_app(RenderApp).world; + let device = render_world .get_resource::() .unwrap() .wgpu_device(); + let queue = render_world.get_resource::().unwrap(); #[cfg(target_arch = "wasm32")] - let format = wgpu::TextureFormat::Rgba8UnormSrgb; + let format = iced_wgpu::wgpu::TextureFormat::Rgba8UnormSrgb; #[cfg(not(target_arch = "wasm32"))] - let format = wgpu::TextureFormat::Bgra8UnormSrgb; + let format = iced_wgpu::wgpu::TextureFormat::Bgra8UnormSrgb; + let mut backend = iced_wgpu::Backend::new(device, queue, config.settings, format); + for font in &config.fonts { + backend.load_font(Cow::Borrowed(*font)); + } Self { - renderer: iced_wgpu::Renderer::new(iced_wgpu::Backend::new( - device, - Default::default(), - format, - )), - debug: Debug::new(), - clipboard: iced_native::clipboard::Null, + renderer: iced_wgpu::Renderer::new(backend), + debug: iced_runtime::Debug::new(), + clipboard: iced_core::clipboard::Null, } } } @@ -147,14 +158,13 @@ fn setup_pipeline(graph: &mut RenderGraph) { ); } -#[doc(hidden)] #[derive(Default)] -pub struct IcedCache { - cache: HashMap>, +struct IcedCache { + cache: HashMap>, } impl IcedCache { - fn get(&mut self) -> &mut Option { + fn get(&mut self) -> &mut Option { let id = TypeId::of::(); if !self.cache.contains_key(&id) { self.cache.insert(id, Some(Default::default())); @@ -170,9 +180,9 @@ pub struct IcedSettings { /// Setting this to `None` defaults to using the `Window`s scale factor. pub scale_factor: Option, /// The theme to use for rendering Iced elements. - pub theme: iced_wgpu::Theme, + pub theme: iced_widget::style::Theme, /// The style to use for rendering Iced elements. - pub style: iced_native::renderer::Style, + pub style: iced::Style, } impl IcedSettings { @@ -186,9 +196,9 @@ impl Default for IcedSettings { fn default() -> Self { Self { scale_factor: None, - theme: iced_wgpu::Theme::Dark, - style: iced_native::renderer::Style { - text_color: iced_native::Color::WHITE, + theme: iced_widget::style::Theme::Dark, + style: iced::Style { + text_color: iced_core::Color::WHITE, }, } } @@ -199,7 +209,7 @@ impl Default for IcedSettings { pub(crate) struct DidDraw(std::sync::atomic::AtomicBool); /// The context for interacting with Iced. Add this as a parameter to your system. -/// ```no_run +/// ```ignore /// fn ui_system(..., mut ctx: IcedContext) { /// let element = ...; // Build your element /// ctx.display(element); @@ -209,7 +219,7 @@ pub(crate) struct DidDraw(std::sync::atomic::AtomicBool); /// `IcedContext` requires an event system to be defined in the [`App`]. /// Do so by invoking `app.add_event::()` when constructing your App. #[derive(SystemParam)] -pub struct IcedContext<'w, 's, Message: Event> { +pub struct IcedContext<'w, 's, Message: bevy_ecs::event::Event> { viewport: Res<'w, ViewportResource>, props: Res<'w, IcedResource>, settings: Res<'w, IcedSettings>, @@ -218,13 +228,12 @@ pub struct IcedContext<'w, 's, Message: Event> { cache_map: NonSendMut<'w, IcedCache>, messages: EventWriter<'w, Message>, did_draw: ResMut<'w, DidDraw>, - #[cfg(feature = "touch")] touches: Res<'w, Touches>, } -impl<'w, 's, M: Event> IcedContext<'w, 's, M> { +impl<'w, 's, M: bevy_ecs::event::Event> IcedContext<'w, 's, M> { /// Display an [`Element`] to the screen. - pub fn display<'a>(&'a mut self, element: impl Into>) { + pub fn display<'a>(&'a mut self, element: impl Into>) { let IcedProps { ref mut renderer, ref mut clipboard, @@ -234,23 +243,16 @@ impl<'w, 's, M: Event> IcedContext<'w, 's, M> { let element = element.into(); - let cursor_position = { + let cursor = { let window = self.windows.single(); - - process_touch_input(self) - .map(|iced_native::Point { x, y }| iced_native::Point { - x: x * bounds.width / window.width(), - y: y * bounds.height / window.height(), - }) - .or_else(|| { - window - .cursor_position() - .map(|Vec2 { x, y }| iced_native::Point { - x: x * bounds.width / window.width(), - y: (window.height() - y) * bounds.height / window.height(), - }) - }) - .unwrap_or(iced_native::Point::ORIGIN) + match window.cursor_position() { + Some(position) => { + Cursor::Available(utils::process_cursor_position(position, bounds, window)) + } + None => utils::process_touch_input(self) + .map(Cursor::Available) + .unwrap_or(Cursor::Unavailable), + } }; let mut messages = Vec::::new(); @@ -259,7 +261,7 @@ impl<'w, 's, M: Event> IcedContext<'w, 's, M> { let mut ui = UserInterface::build(element, bounds, cache, renderer); let (_, _event_statuses) = ui.update( self.events.as_slice(), - cursor_position, + cursor, renderer, clipboard, &mut messages, @@ -267,12 +269,7 @@ impl<'w, 's, M: Event> IcedContext<'w, 's, M> { messages.into_iter().for_each(|msg| self.messages.send(msg)); - ui.draw( - renderer, - &self.settings.theme, - &self.settings.style, - cursor_position, - ); + ui.draw(renderer, &self.settings.theme, &self.settings.style, cursor); self.events.clear(); *cache_entry = Some(ui.into_cache()); @@ -280,40 +277,3 @@ impl<'w, 's, M: Event> IcedContext<'w, 's, M> { .store(true, std::sync::atomic::Ordering::Relaxed); } } - -#[cfg(feature = "touch")] -/// To correctly process input as last resort events are used -fn process_touch_input(context: &IcedContext) -> Option { - context - .touches - .first_pressed_position() - .or(context - .touches - .iter_just_released() - .map(|touch| touch.position()) - .next()) - .map(|Vec2 { x, y }| iced_native::Point { x, y }) - .or(context - .events - .iter() - .filter_map(|ev| { - if let iced_native::Event::Touch( - iced_native::touch::Event::FingerLifted { position, .. } - | iced_native::touch::Event::FingerLost { position, .. } - | iced_native::touch::Event::FingerMoved { position, .. } - | iced_native::touch::Event::FingerPressed { position, .. }, - ) = ev - { - Some(position) - } else { - None - } - }) - .next() - .copied()) -} - -#[cfg(not(feature = "touch"))] -fn process_touch_input(_: &IcedContext) -> Option { - None -} diff --git a/src/render.rs b/src/render.rs index afc2d05..ccd39d1 100644 --- a/src/render.rs +++ b/src/render.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ system::{Commands, Res, Resource}, world::World, }; -use bevy_render::renderer::RenderDevice; +use bevy_render::renderer::{RenderDevice, RenderQueue}; use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext}, renderer::RenderContext, @@ -12,8 +12,9 @@ use bevy_render::{ Extract, }; use bevy_window::Window; -use iced_native::Size; -use iced_wgpu::{wgpu::util::StagingBelt, Viewport}; +use iced_core::Size; +use iced_graphics::Viewport; +use iced_wgpu::wgpu::util::StagingBelt; use std::sync::Mutex; use crate::{DidDraw, IcedProps, IcedResource, IcedSettings}; @@ -80,12 +81,17 @@ impl Node for IcedNode { .unwrap() .windows .values() - .next() else { return Ok(()) }; + .next() + else { + return Ok(()); + }; let IcedProps { renderer, debug, .. } = &mut *world.resource::().lock().unwrap(); - let render_device = world.resource::(); + let render_device = world.resource::().wgpu_device(); + let render_queue = world.resource::(); + let viewport = world.resource::(); if !world .get_resource::() @@ -94,18 +100,15 @@ impl Node for IcedNode { { return Ok(()); } - - let view = extracted_window.swap_chain_texture.as_ref().unwrap(); + let view = extracted_window.swap_chain_texture_view.as_ref().unwrap(); let staging_belt = &mut *self.staging_belt.lock().unwrap(); - let viewport = world.resource::(); - let device = render_device.wgpu_device(); - renderer.with_primitives(|backend, primitives| { backend.present( - device, - staging_belt, + render_device, + render_queue, render_context.command_encoder(), + None, view, primitives, viewport, diff --git a/src/systems.rs b/src/systems.rs index bf877e9..bee877c 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -5,7 +5,6 @@ use bevy_ecs::{ system::{Res, ResMut, Resource, SystemParam}, }; use bevy_input::keyboard::KeyCode; -#[cfg(feature = "touch")] use bevy_input::touch::TouchInput; use bevy_input::{ keyboard::KeyboardInput, @@ -13,10 +12,10 @@ use bevy_input::{ ButtonState, Input, }; use bevy_window::{CursorEntered, CursorLeft, CursorMoved, ReceivedCharacter}; -use iced_native::{keyboard, mouse, Event as IcedEvent, Point}; +use iced_core::{keyboard, mouse, Event as IcedEvent, Point}; #[derive(Resource, Deref, DerefMut, Default)] -pub struct IcedEventQueue(Vec); +pub struct IcedEventQueue(Vec); #[derive(SystemParam)] pub struct InputEvents<'w, 's> { @@ -27,22 +26,21 @@ pub struct InputEvents<'w, 's> { mouse_wheel: EventReader<'w, 's, MouseWheel>, received_character: EventReader<'w, 's, ReceivedCharacter>, keyboard_input: EventReader<'w, 's, KeyboardInput>, - #[cfg(feature = "touch")] touch_input: EventReader<'w, 's, TouchInput>, } fn compute_modifiers(input_map: &Input) -> keyboard::Modifiers { let mut modifiers = keyboard::Modifiers::default(); - if input_map.any_pressed([KeyCode::LControl, KeyCode::RControl]) { + if input_map.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]) { modifiers |= keyboard::Modifiers::CTRL; } - if input_map.any_pressed([KeyCode::LShift, KeyCode::RShift]) { + if input_map.any_pressed([KeyCode::ShiftLeft, KeyCode::ShiftRight]) { modifiers |= keyboard::Modifiers::SHIFT; } - if input_map.any_pressed([KeyCode::LAlt, KeyCode::RAlt]) { + if input_map.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]) { modifiers |= keyboard::Modifiers::ALT; } - if input_map.any_pressed([KeyCode::LWin, KeyCode::RWin]) { + if input_map.any_pressed([KeyCode::SuperLeft, KeyCode::SuperRight]) { modifiers |= keyboard::Modifiers::LOGO; } modifiers @@ -64,28 +62,28 @@ pub fn process_input( for ev in events.mouse_button.iter() { let button = conversions::mouse_button(ev.button); event_queue.push(IcedEvent::Mouse(match ev.state { - ButtonState::Pressed => iced_native::mouse::Event::ButtonPressed(button), - ButtonState::Released => iced_native::mouse::Event::ButtonReleased(button), + ButtonState::Pressed => iced_core::mouse::Event::ButtonPressed(button), + ButtonState::Released => iced_core::mouse::Event::ButtonReleased(button), })) } for _ev in events.cursor_entered.iter() { - event_queue.push(IcedEvent::Mouse(iced_native::mouse::Event::CursorEntered)); + event_queue.push(IcedEvent::Mouse(iced_core::mouse::Event::CursorEntered)); } for _ev in events.cursor_left.iter() { - event_queue.push(IcedEvent::Mouse(iced_native::mouse::Event::CursorLeft)); + event_queue.push(IcedEvent::Mouse(iced_core::mouse::Event::CursorLeft)); } for ev in events.mouse_wheel.iter() { - event_queue.push(IcedEvent::Mouse(iced_native::mouse::Event::WheelScrolled { + event_queue.push(IcedEvent::Mouse(iced_core::mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Pixels { x: ev.x, y: ev.y }, })); } for ev in events.received_character.iter() { event_queue.push(IcedEvent::Keyboard( - iced_native::keyboard::Event::CharacterReceived(ev.char), + iced_core::keyboard::Event::CharacterReceived(ev.char), )); } @@ -94,14 +92,14 @@ pub fn process_input( use keyboard::Event::*; let modifiers = compute_modifiers(&input_map); let event = match code { - KeyCode::LControl - | KeyCode::RControl - | KeyCode::LShift - | KeyCode::RShift - | KeyCode::LAlt - | KeyCode::RAlt - | KeyCode::LWin - | KeyCode::RWin => ModifiersChanged(modifiers), + KeyCode::ControlLeft + | KeyCode::ControlRight + | KeyCode::ShiftLeft + | KeyCode::ShiftRight + | KeyCode::AltLeft + | KeyCode::AltRight + | KeyCode::SuperLeft + | KeyCode::SuperRight => ModifiersChanged(modifiers), code => { let key_code = conversions::key_code(code); if ev.state.is_pressed() { @@ -122,7 +120,6 @@ pub fn process_input( } } - #[cfg(feature = "touch")] for ev in events.touch_input.iter() { event_queue.push(IcedEvent::Touch(conversions::touch_event(ev))); } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..cdeb638 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,48 @@ +use crate::iced; +use crate::IcedContext; +use bevy_math::Vec2; +use bevy_window::Window; + +pub(crate) fn process_cursor_position( + position: Vec2, + bounds: iced_core::Size, + window: &Window, +) -> iced_core::Point { + iced_core::Point { + x: position.x * bounds.width / window.width(), + y: position.y * bounds.height / window.height(), + } +} + +/// To correctly process input as last resort events are used +pub(crate) fn process_touch_input( + context: &IcedContext, +) -> Option { + context + .touches + .first_pressed_position() + .or(context + .touches + .iter_just_released() + .map(|touch| touch.position()) + .next()) + .map(|Vec2 { x, y }| iced::Point { x, y }) + .or(context + .events + .iter() + .filter_map(|ev| { + if let iced::Event::Touch( + iced::touch::Event::FingerLifted { position, .. } + | iced::touch::Event::FingerLost { position, .. } + | iced::touch::Event::FingerMoved { position, .. } + | iced::touch::Event::FingerPressed { position, .. }, + ) = ev + { + Some(position) + } else { + None + } + }) + .next() + .copied()) +}