diff options
121 files changed, 4029 insertions, 2331 deletions
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 80bbcacd..57169796 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -27,4 +27,4 @@ jobs:      - name: Delete `web-sys` dependency from `integration` example        run: sed -i '$d' examples/integration/Cargo.toml      - name: Find outdated dependencies -      run: cargo outdated --workspace --exit-code 1 +      run: cargo outdated --workspace --exit-code 1 --ignore raw-window-handle diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 62e28ca3..35bf10f4 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -8,7 +8,7 @@ jobs:      steps:      - uses: hecrj/setup-rust-action@v1        with: -        rust-version: nightly +        rust-version: nightly-2023-12-11      - uses: actions/checkout@v2      - name: Generate documentation        run: | @@ -126,11 +126,10 @@ bytemuck = { version = "1.0", features = ["derive"] }  cosmic-text = "0.10"  futures = "0.3"  glam = "0.24" -glyphon = { git = "https://github.com/grovesNL/glyphon.git", rev = "2caa9fc5e5923c1d827d177c3619cab7e9885b85" } +glyphon = "0.4"  guillotiere = "0.6"  half = "2.2"  image = "0.24" -instant = "0.1"  kamadak-exif = "0.5"  kurbo = "0.9"  log = "0.4" @@ -145,6 +144,7 @@ raw-window-handle = "0.5"  resvg = "0.36"  rustc-hash = "1.0"  smol = "1.0" +smol_str = "0.2"  softbuffer = "0.2"  syntect = "5.1"  sysinfo = "0.28" @@ -157,7 +157,8 @@ unicode-segmentation = "1.0"  wasm-bindgen-futures = "0.4"  wasm-timer = "0.2"  web-sys = "0.3" +web-time = "0.2"  wgpu = "0.18"  winapi = "0.3"  window_clipboard = "0.3" -winit = { git = "https://github.com/iced-rs/winit.git", rev = "c52db2045d0a2f1b8d9923870de1d4ab1994146e", default-features = false } +winit = { git = "https://github.com/iced-rs/winit.git", rev = "b91e39ece2c0d378c3b80da7f3ab50e17bb798a5", features = ["rwh_05"] } diff --git a/core/Cargo.toml b/core/Cargo.toml index 4672c754..be92a572 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,18 +13,18 @@ keywords.workspace = true  [dependencies]  bitflags.workspace = true  log.workspace = true +num-traits.workspace = true +smol_str.workspace = true  thiserror.workspace = true +web-time.workspace = true  xxhash-rust.workspace = true -num-traits.workspace = true  palette.workspace = true  palette.optional = true -[target.'cfg(target_arch = "wasm32")'.dependencies] -instant.workspace = true -  [target.'cfg(windows)'.dependencies] -raw-window-handle.workspace = true +# TODO: Use `workspace` dependency once `wgpu` upgrades `raw-window-handle` +raw-window-handle = "0.6"  [dev-dependencies]  approx = "0.5" diff --git a/core/src/element.rs b/core/src/element.rs index dea111af..8b510218 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -6,7 +6,7 @@ use crate::renderer;  use crate::widget;  use crate::widget::tree::{self, Tree};  use crate::{ -    Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget, +    Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, Widget,  };  use std::any::Any; @@ -296,12 +296,8 @@ where          self.widget.diff(tree);      } -    fn width(&self) -> Length { -        self.widget.width() -    } - -    fn height(&self) -> Length { -        self.widget.height() +    fn size(&self) -> Size<Length> { +        self.widget.size()      }      fn layout( @@ -466,12 +462,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>  where      Renderer: crate::Renderer,  { -    fn width(&self) -> Length { -        self.element.widget.width() -    } - -    fn height(&self) -> Length { -        self.element.widget.height() +    fn size(&self) -> Size<Length> { +        self.element.widget.size()      }      fn tag(&self) -> tree::Tag { diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs index 4c6ca08d..b810ccb0 100644 --- a/core/src/keyboard.rs +++ b/core/src/keyboard.rs @@ -1,8 +1,11 @@  //! Listen to keyboard events. +pub mod key; +  mod event; -mod key_code; +mod location;  mod modifiers;  pub use event::Event; -pub use key_code::KeyCode; +pub use key::Key; +pub use location::Location;  pub use modifiers::Modifiers; diff --git a/core/src/keyboard/event.rs b/core/src/keyboard/event.rs index 016761af..1eb42334 100644 --- a/core/src/keyboard/event.rs +++ b/core/src/keyboard/event.rs @@ -1,4 +1,5 @@ -use super::{KeyCode, Modifiers}; +use crate::keyboard::{Key, Location, Modifiers}; +use crate::SmolStr;  /// A keyboard event.  /// @@ -6,29 +7,35 @@ use super::{KeyCode, Modifiers};  /// additional events, feel free to [open an issue] and share your use case!_  ///  /// [open an issue]: https://github.com/iced-rs/iced/issues -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)]  pub enum Event {      /// A keyboard key was pressed.      KeyPressed { -        /// The key identifier -        key_code: KeyCode, +        /// The key pressed. +        key: Key, -        /// The state of the modifier keys +        /// The location of the key. +        location: Location, + +        /// The state of the modifier keys.          modifiers: Modifiers, + +        /// The text produced by the key press, if any. +        text: Option<SmolStr>,      },      /// A keyboard key was released.      KeyReleased { -        /// The key identifier -        key_code: KeyCode, +        /// The key released. +        key: Key, -        /// The state of the modifier keys +        /// The location of the key. +        location: Location, + +        /// The state of the modifier keys.          modifiers: Modifiers,      }, -    /// A unicode character was received. -    CharacterReceived(char), -      /// The keyboard modifiers have changed.      ModifiersChanged(Modifiers),  } diff --git a/core/src/keyboard/key.rs b/core/src/keyboard/key.rs new file mode 100644 index 00000000..dbde5196 --- /dev/null +++ b/core/src/keyboard/key.rs @@ -0,0 +1,744 @@ +//! Identify keyboard keys. +use crate::SmolStr; + +/// A key on the keyboard. +/// +/// This is mostly the `Key` type found in [`winit`]. +/// +/// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Key<C = SmolStr> { +    /// A key with an established name. +    Named(Named), + +    /// A key string that corresponds to the character typed by the user, taking into account the +    /// user’s current locale setting, and any system-level keyboard mapping overrides that are in +    /// effect. +    Character(C), + +    /// An unidentified key. +    Unidentified, +} + +impl Key { +    /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on +    /// `Key`. All other variants remain unchanged. +    pub fn as_ref(&self) -> Key<&str> { +        match self { +            Self::Named(named) => Key::Named(*named), +            Self::Character(c) => Key::Character(c.as_ref()), +            Self::Unidentified => Key::Unidentified, +        } +    } +} + +/// A named key. +/// +/// This is mostly the `NamedKey` type found in [`winit`]. +/// +/// [`winit`]: https://docs.rs/winit/0.29.10/winit/keyboard/enum.Key.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[allow(missing_docs)] +pub enum Named { +    /// The `Alt` (Alternative) key. +    /// +    /// This key enables the alternate modifier function for interpreting concurrent or subsequent +    /// keyboard input. This key value is also used for the Apple <kbd>Option</kbd> key. +    Alt, +    /// The Alternate Graphics (<kbd>AltGr</kbd> or <kbd>AltGraph</kbd>) key. +    /// +    /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the +    /// level 2 modifier). +    AltGraph, +    /// The `Caps Lock` (Capital) key. +    /// +    /// Toggle capital character lock function for interpreting subsequent keyboard input event. +    CapsLock, +    /// The `Control` or `Ctrl` key. +    /// +    /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard +    /// input. +    Control, +    /// The Function switch `Fn` key. Activating this key simultaneously with another key changes +    /// that key’s value to an alternate character or function. This key is often handled directly +    /// in the keyboard hardware and does not usually generate key events. +    Fn, +    /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the +    /// keyboard to changes some keys' values to an alternate character or function. This key is +    /// often handled directly in the keyboard hardware and does not usually generate key events. +    FnLock, +    /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting +    /// subsequent keyboard input. +    NumLock, +    /// Toggle between scrolling and cursor movement modes. +    ScrollLock, +    /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard +    /// input. +    Shift, +    /// The Symbol modifier key (used on some virtual keyboards). +    Symbol, +    SymbolLock, +    // Legacy modifier key. Also called "Super" in certain places. +    Meta, +    // Legacy modifier key. +    Hyper, +    /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard +    /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. +    /// +    /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. +    Super, +    /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key +    /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for +    /// the Android `KEYCODE_DPAD_CENTER`. +    Enter, +    /// The Horizontal Tabulation `Tab` key. +    Tab, +    /// Used in text to insert a space between words. Usually located below the character keys. +    Space, +    /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) +    ArrowDown, +    /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) +    ArrowLeft, +    /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) +    ArrowRight, +    /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) +    ArrowUp, +    /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). +    End, +    /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). +    /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. +    /// +    /// [`GoHome`]: Self::GoHome +    Home, +    /// Scroll down or display next page of content. +    PageDown, +    /// Scroll up or display previous page of content. +    PageUp, +    /// Used to remove the character to the left of the cursor. This key value is also used for +    /// the key labeled `Delete` on MacOS keyboards. +    Backspace, +    /// Remove the currently selected input. +    Clear, +    /// Copy the current selection. (`APPCOMMAND_COPY`) +    Copy, +    /// The Cursor Select key. +    CrSel, +    /// Cut the current selection. (`APPCOMMAND_CUT`) +    Cut, +    /// Used to delete the character to the right of the cursor. This key value is also used for the +    /// key labeled `Delete` on MacOS keyboards when `Fn` is active. +    Delete, +    /// The Erase to End of Field key. This key deletes all characters from the current cursor +    /// position to the end of the current field. +    EraseEof, +    /// The Extend Selection (Exsel) key. +    ExSel, +    /// Toggle between text modes for insertion or overtyping. +    /// (`KEYCODE_INSERT`) +    Insert, +    /// The Paste key. (`APPCOMMAND_PASTE`) +    Paste, +    /// Redo the last action. (`APPCOMMAND_REDO`) +    Redo, +    /// Undo the last action. (`APPCOMMAND_UNDO`) +    Undo, +    /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. +    Accept, +    /// Redo or repeat an action. +    Again, +    /// The Attention (Attn) key. +    Attn, +    Cancel, +    /// Show the application’s context menu. +    /// This key is commonly found between the right `Super` key and the right `Control` key. +    ContextMenu, +    /// The `Esc` key. This key was originally used to initiate an escape sequence, but is +    /// now more generally used to exit or "escape" the current context, such as closing a dialog +    /// or exiting full screen mode. +    Escape, +    Execute, +    /// Open the Find dialog. (`APPCOMMAND_FIND`) +    Find, +    /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, +    /// `KEYCODE_HELP`) +    Help, +    /// Pause the current state or application (as appropriate). +    /// +    /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` +    /// instead. +    Pause, +    /// Play or resume the current state or application (as appropriate). +    /// +    /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` +    /// instead. +    Play, +    /// The properties (Props) key. +    Props, +    Select, +    /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) +    ZoomIn, +    /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) +    ZoomOut, +    /// The Brightness Down key. Typically controls the display brightness. +    /// (`KEYCODE_BRIGHTNESS_DOWN`) +    BrightnessDown, +    /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) +    BrightnessUp, +    /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) +    Eject, +    LogOff, +    /// Toggle power state. (`KEYCODE_POWER`) +    /// Note: Note: Some devices might not expose this key to the operating environment. +    Power, +    /// The `PowerOff` key. Sometime called `PowerDown`. +    PowerOff, +    /// Initiate print-screen function. +    PrintScreen, +    /// The Hibernate key. This key saves the current state of the computer to disk so that it can +    /// be restored. The computer will then shutdown. +    Hibernate, +    /// The Standby key. This key turns off the display and places the computer into a low-power +    /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. +    /// (`KEYCODE_SLEEP`) +    Standby, +    /// The WakeUp key. (`KEYCODE_WAKEUP`) +    WakeUp, +    /// Initate the multi-candidate mode. +    AllCandidates, +    Alphanumeric, +    /// Initiate the Code Input mode to allow characters to be entered by +    /// their code points. +    CodeInput, +    /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a +    /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to +    /// produce a different character. +    Compose, +    /// Convert the current input method sequence. +    Convert, +    /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. +    FinalMode, +    /// Switch to the first character group. (ISO/IEC 9995) +    GroupFirst, +    /// Switch to the last character group. (ISO/IEC 9995) +    GroupLast, +    /// Switch to the next character group. (ISO/IEC 9995) +    GroupNext, +    /// Switch to the previous character group. (ISO/IEC 9995) +    GroupPrevious, +    /// Toggle between or cycle through input modes of IMEs. +    ModeChange, +    NextCandidate, +    /// Accept current input method sequence without +    /// conversion in IMEs. +    NonConvert, +    PreviousCandidate, +    Process, +    SingleCandidate, +    /// Toggle between Hangul and English modes. +    HangulMode, +    HanjaMode, +    JunjaMode, +    /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. +    /// (`KEYCODE_EISU`) +    Eisu, +    /// The (Half-Width) Characters key. +    Hankaku, +    /// The Hiragana (Japanese Kana characters) key. +    Hiragana, +    /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) +    HiraganaKatakana, +    /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from +    /// romaji mode). +    KanaMode, +    /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is +    /// typically used to switch to a hiragana keyboard for the purpose of converting input into +    /// kanji. (`KEYCODE_KANA`) +    KanjiMode, +    /// The Katakana (Japanese Kana characters) key. +    Katakana, +    /// The Roman characters function key. +    Romaji, +    /// The Zenkaku (Full-Width) Characters key. +    Zenkaku, +    /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) +    ZenkakuHankaku, +    /// General purpose virtual function key, as index 1. +    Soft1, +    /// General purpose virtual function key, as index 2. +    Soft2, +    /// General purpose virtual function key, as index 3. +    Soft3, +    /// General purpose virtual function key, as index 4. +    Soft4, +    /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, +    /// `KEYCODE_CHANNEL_DOWN`) +    ChannelDown, +    /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, +    /// `KEYCODE_CHANNEL_UP`) +    ChannelUp, +    /// Close the current document or message (Note: This doesn’t close the application). +    /// (`APPCOMMAND_CLOSE`) +    Close, +    /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) +    MailForward, +    /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) +    MailReply, +    /// Send the current message. (`APPCOMMAND_SEND_MAIL`) +    MailSend, +    /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) +    MediaClose, +    /// Initiate or continue forward playback at faster than normal speed, or increase speed if +    /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) +    MediaFastForward, +    /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) +    /// +    /// Note: Media controller devices should use this value rather than `"Pause"` for their pause +    /// keys. +    MediaPause, +    /// Initiate or continue media playback at normal speed, if not currently playing at normal +    /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) +    MediaPlay, +    /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, +    /// `KEYCODE_MEDIA_PLAY_PAUSE`) +    MediaPlayPause, +    /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, +    /// `KEYCODE_MEDIA_RECORD`) +    MediaRecord, +    /// Initiate or continue reverse playback at faster than normal speed, or increase speed if +    /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) +    MediaRewind, +    /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. +    /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) +    MediaStop, +    /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) +    MediaTrackNext, +    /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, +    /// `KEYCODE_MEDIA_PREVIOUS`) +    MediaTrackPrevious, +    /// Open a new document or message. (`APPCOMMAND_NEW`) +    New, +    /// Open an existing document or message. (`APPCOMMAND_OPEN`) +    Open, +    /// Print the current document or message. (`APPCOMMAND_PRINT`) +    Print, +    /// Save the current document or message. (`APPCOMMAND_SAVE`) +    Save, +    /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) +    SpellCheck, +    /// The `11` key found on media numpads that +    /// have buttons from `1` ... `12`. +    Key11, +    /// The `12` key found on media numpads that +    /// have buttons from `1` ... `12`. +    Key12, +    /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) +    AudioBalanceLeft, +    /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) +    AudioBalanceRight, +    /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, +    /// `VK_BASS_BOOST_DOWN`) +    AudioBassBoostDown, +    /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) +    AudioBassBoostToggle, +    /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, +    /// `VK_BASS_BOOST_UP`) +    AudioBassBoostUp, +    /// Adjust audio fader towards front. (`VK_FADER_FRONT`) +    AudioFaderFront, +    /// Adjust audio fader towards rear. (`VK_FADER_REAR`) +    AudioFaderRear, +    /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) +    AudioSurroundModeNext, +    /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) +    AudioTrebleDown, +    /// Increase treble. (`APPCOMMAND_TREBLE_UP`) +    AudioTrebleUp, +    /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) +    AudioVolumeDown, +    /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) +    AudioVolumeUp, +    /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, +    /// `KEYCODE_VOLUME_MUTE`) +    AudioVolumeMute, +    /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) +    MicrophoneToggle, +    /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) +    MicrophoneVolumeDown, +    /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) +    MicrophoneVolumeUp, +    /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) +    MicrophoneVolumeMute, +    /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) +    SpeechCorrectionList, +    /// Toggle between dictation mode and command/control mode. +    /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) +    SpeechInputToggle, +    /// The first generic "LaunchApplication" key. This is commonly associated with launching "My +    /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) +    LaunchApplication1, +    /// The second generic "LaunchApplication" key. This is commonly associated with launching +    /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, +    /// `KEYCODE_CALCULATOR`) +    LaunchApplication2, +    /// The "Calendar" key. (`KEYCODE_CALENDAR`) +    LaunchCalendar, +    /// The "Contacts" key. (`KEYCODE_CONTACTS`) +    LaunchContacts, +    /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) +    LaunchMail, +    /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) +    LaunchMediaPlayer, +    LaunchMusicPlayer, +    LaunchPhone, +    LaunchScreenSaver, +    LaunchSpreadsheet, +    LaunchWebBrowser, +    LaunchWebCam, +    LaunchWordProcessor, +    /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) +    BrowserBack, +    /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) +    BrowserFavorites, +    /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) +    BrowserForward, +    /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) +    BrowserHome, +    /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) +    BrowserRefresh, +    /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) +    BrowserSearch, +    /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) +    BrowserStop, +    /// The Application switch key, which provides a list of recent apps to switch between. +    /// (`KEYCODE_APP_SWITCH`) +    AppSwitch, +    /// The Call key. (`KEYCODE_CALL`) +    Call, +    /// The Camera key. (`KEYCODE_CAMERA`) +    Camera, +    /// The Camera focus key. (`KEYCODE_FOCUS`) +    CameraFocus, +    /// The End Call key. (`KEYCODE_ENDCALL`) +    EndCall, +    /// The Back key. (`KEYCODE_BACK`) +    GoBack, +    /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) +    GoHome, +    /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) +    HeadsetHook, +    LastNumberRedial, +    /// The Notification key. (`KEYCODE_NOTIFICATION`) +    Notification, +    /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) +    MannerMode, +    VoiceDial, +    /// Switch to viewing TV. (`KEYCODE_TV`) +    TV, +    /// TV 3D Mode. (`KEYCODE_3D_MODE`) +    TV3DMode, +    /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) +    TVAntennaCable, +    /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) +    TVAudioDescription, +    /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) +    TVAudioDescriptionMixDown, +    /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) +    TVAudioDescriptionMixUp, +    /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) +    TVContentsMenu, +    /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) +    TVDataService, +    /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) +    TVInput, +    /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) +    TVInputComponent1, +    /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) +    TVInputComponent2, +    /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) +    TVInputComposite1, +    /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) +    TVInputComposite2, +    /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) +    TVInputHDMI1, +    /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) +    TVInputHDMI2, +    /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) +    TVInputHDMI3, +    /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) +    TVInputHDMI4, +    /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) +    TVInputVGA1, +    /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) +    TVMediaContext, +    /// Toggle network. (`KEYCODE_TV_NETWORK`) +    TVNetwork, +    /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) +    TVNumberEntry, +    /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) +    TVPower, +    /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) +    TVRadioService, +    /// Satellite. (`KEYCODE_TV_SATELLITE`) +    TVSatellite, +    /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) +    TVSatelliteBS, +    /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) +    TVSatelliteCS, +    /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) +    TVSatelliteToggle, +    /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) +    TVTerrestrialAnalog, +    /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) +    TVTerrestrialDigital, +    /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) +    TVTimer, +    /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) +    AVRInput, +    /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) +    AVRPower, +    /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, +    /// `KEYCODE_PROG_RED`) +    ColorF0Red, +    /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, +    /// `KEYCODE_PROG_GREEN`) +    ColorF1Green, +    /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, +    /// `KEYCODE_PROG_YELLOW`) +    ColorF2Yellow, +    /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, +    /// `KEYCODE_PROG_BLUE`) +    ColorF3Blue, +    /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) +    ColorF4Grey, +    /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) +    ColorF5Brown, +    /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) +    ClosedCaptionToggle, +    /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) +    Dimmer, +    /// Swap video sources. (`VK_DISPLAY_SWAP`) +    DisplaySwap, +    /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) +    DVR, +    /// Exit the current application. (`VK_EXIT`) +    Exit, +    /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) +    FavoriteClear0, +    /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) +    FavoriteClear1, +    /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) +    FavoriteClear2, +    /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) +    FavoriteClear3, +    /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) +    FavoriteRecall0, +    /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) +    FavoriteRecall1, +    /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) +    FavoriteRecall2, +    /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) +    FavoriteRecall3, +    /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) +    FavoriteStore0, +    /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) +    FavoriteStore1, +    /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) +    FavoriteStore2, +    /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) +    FavoriteStore3, +    /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) +    Guide, +    /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) +    GuideNextDay, +    /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) +    GuidePreviousDay, +    /// Toggle display of information about currently selected context or media. (`VK_INFO`, +    /// `KEYCODE_INFO`) +    Info, +    /// Toggle instant replay. (`VK_INSTANT_REPLAY`) +    InstantReplay, +    /// Launch linked content, if available and appropriate. (`VK_LINK`) +    Link, +    /// List the current program. (`VK_LIST`) +    ListProgram, +    /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) +    LiveContent, +    /// Lock or unlock current content or program. (`VK_LOCK`) +    Lock, +    /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) +    /// +    /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, +    /// which is encoded as `"ContextMenu"`. +    MediaApps, +    /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) +    MediaAudioTrack, +    /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) +    MediaLast, +    /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) +    MediaSkipBackward, +    /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) +    MediaSkipForward, +    /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) +    MediaStepBackward, +    /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) +    MediaStepForward, +    /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) +    MediaTopMenu, +    /// Navigate in. (`KEYCODE_NAVIGATE_IN`) +    NavigateIn, +    /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) +    NavigateNext, +    /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) +    NavigateOut, +    /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) +    NavigatePrevious, +    /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) +    NextFavoriteChannel, +    /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) +    NextUserProfile, +    /// Access on-demand content or programs. (`VK_ON_DEMAND`) +    OnDemand, +    /// Pairing key to pair devices. (`KEYCODE_PAIRING`) +    Pairing, +    /// Move picture-in-picture window down. (`VK_PINP_DOWN`) +    PinPDown, +    /// Move picture-in-picture window. (`VK_PINP_MOVE`) +    PinPMove, +    /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) +    PinPToggle, +    /// Move picture-in-picture window up. (`VK_PINP_UP`) +    PinPUp, +    /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) +    PlaySpeedDown, +    /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) +    PlaySpeedReset, +    /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) +    PlaySpeedUp, +    /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) +    RandomToggle, +    /// Not a physical key, but this key code is sent when the remote control battery is low. +    /// (`VK_RC_LOW_BATTERY`) +    RcLowBattery, +    /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) +    RecordSpeedNext, +    /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). +    /// (`VK_RF_BYPASS`) +    RfBypass, +    /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) +    ScanChannelsToggle, +    /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) +    ScreenModeNext, +    /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) +    Settings, +    /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) +    SplitScreenToggle, +    /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) +    STBInput, +    /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) +    STBPower, +    /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) +    Subtitle, +    /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). +    Teletext, +    /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) +    VideoModeNext, +    /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) +    Wink, +    /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, +    /// `KEYCODE_TV_ZOOM_MODE`) +    ZoomToggle, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F1, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F2, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F3, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F4, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F5, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F6, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F7, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F8, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F9, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F10, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F11, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F12, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F13, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F14, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F15, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F16, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F17, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F18, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F19, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F20, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F21, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F22, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F23, +    /// General-purpose function key. +    /// Usually found at the top of the keyboard. +    F24, +    /// General-purpose function key. +    F25, +    /// General-purpose function key. +    F26, +    /// General-purpose function key. +    F27, +    /// General-purpose function key. +    F28, +    /// General-purpose function key. +    F29, +    /// General-purpose function key. +    F30, +    /// General-purpose function key. +    F31, +    /// General-purpose function key. +    F32, +    /// General-purpose function key. +    F33, +    /// General-purpose function key. +    F34, +    /// General-purpose function key. +    F35, +} diff --git a/core/src/keyboard/key_code.rs b/core/src/keyboard/key_code.rs deleted file mode 100644 index 74ead170..00000000 --- a/core/src/keyboard/key_code.rs +++ /dev/null @@ -1,203 +0,0 @@ -/// The symbolic name of a keyboard key. -/// -/// This is mostly the `KeyCode` type found in [`winit`]. -/// -/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ -#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] -#[repr(u32)] -#[allow(missing_docs)] -pub enum KeyCode { -    /// The '1' key over the letters. -    Key1, -    /// The '2' key over the letters. -    Key2, -    /// The '3' key over the letters. -    Key3, -    /// The '4' key over the letters. -    Key4, -    /// The '5' key over the letters. -    Key5, -    /// The '6' key over the letters. -    Key6, -    /// The '7' key over the letters. -    Key7, -    /// The '8' key over the letters. -    Key8, -    /// The '9' key over the letters. -    Key9, -    /// The '0' key over the 'O' and 'P' keys. -    Key0, - -    A, -    B, -    C, -    D, -    E, -    F, -    G, -    H, -    I, -    J, -    K, -    L, -    M, -    N, -    O, -    P, -    Q, -    R, -    S, -    T, -    U, -    V, -    W, -    X, -    Y, -    Z, - -    /// The Escape key, next to F1. -    Escape, - -    F1, -    F2, -    F3, -    F4, -    F5, -    F6, -    F7, -    F8, -    F9, -    F10, -    F11, -    F12, -    F13, -    F14, -    F15, -    F16, -    F17, -    F18, -    F19, -    F20, -    F21, -    F22, -    F23, -    F24, - -    /// Print Screen/SysRq. -    Snapshot, -    /// Scroll Lock. -    Scroll, -    /// Pause/Break key, next to Scroll lock. -    Pause, - -    /// `Insert`, next to Backspace. -    Insert, -    Home, -    Delete, -    End, -    PageDown, -    PageUp, - -    Left, -    Up, -    Right, -    Down, - -    /// The Backspace key, right over Enter. -    Backspace, -    /// The Enter key. -    Enter, -    /// The space bar. -    Space, - -    /// The "Compose" key on Linux. -    Compose, - -    Caret, - -    Numlock, -    Numpad0, -    Numpad1, -    Numpad2, -    Numpad3, -    Numpad4, -    Numpad5, -    Numpad6, -    Numpad7, -    Numpad8, -    Numpad9, -    NumpadAdd, -    NumpadDivide, -    NumpadDecimal, -    NumpadComma, -    NumpadEnter, -    NumpadEquals, -    NumpadMultiply, -    NumpadSubtract, - -    AbntC1, -    AbntC2, -    Apostrophe, -    Apps, -    Asterisk, -    At, -    Ax, -    Backslash, -    Calculator, -    Capital, -    Colon, -    Comma, -    Convert, -    Equals, -    Grave, -    Kana, -    Kanji, -    LAlt, -    LBracket, -    LControl, -    LShift, -    LWin, -    Mail, -    MediaSelect, -    MediaStop, -    Minus, -    Mute, -    MyComputer, -    NavigateForward,  // also called "Next" -    NavigateBackward, // also called "Prior" -    NextTrack, -    NoConvert, -    OEM102, -    Period, -    PlayPause, -    Plus, -    Power, -    PrevTrack, -    RAlt, -    RBracket, -    RControl, -    RShift, -    RWin, -    Semicolon, -    Slash, -    Sleep, -    Stop, -    Sysrq, -    Tab, -    Underline, -    Unlabeled, -    VolumeDown, -    VolumeUp, -    Wake, -    WebBack, -    WebFavorites, -    WebForward, -    WebHome, -    WebRefresh, -    WebSearch, -    WebStop, -    Yen, -    Copy, -    Paste, -    Cut, -} diff --git a/core/src/keyboard/location.rs b/core/src/keyboard/location.rs new file mode 100644 index 00000000..feff0820 --- /dev/null +++ b/core/src/keyboard/location.rs @@ -0,0 +1,12 @@ +/// The location of a key on the keyboard. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Location { +    /// The standard group of keys on the keyboard. +    Standard, +    /// The left side of the keyboard. +    Left, +    /// The right side of the keyboard. +    Right, +    /// The numpad of the keyboard. +    Numpad, +} diff --git a/core/src/layout.rs b/core/src/layout.rs index caf315b6..95720aba 100644 --- a/core/src/layout.rs +++ b/core/src/layout.rs @@ -7,7 +7,7 @@ pub mod flex;  pub use limits::Limits;  pub use node::Node; -use crate::{Point, Rectangle, Size, Vector}; +use crate::{Length, Padding, Point, Rectangle, Size, Vector};  /// The bounds of a [`Node`] and its children, using absolute coordinates.  #[derive(Debug, Clone, Copy)] @@ -71,12 +71,12 @@ pub fn next_to_each_other(      left: impl FnOnce(&Limits) -> Node,      right: impl FnOnce(&Limits) -> Node,  ) -> Node { -    let mut left_node = left(limits); +    let left_node = left(limits);      let left_size = left_node.size();      let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0)); -    let mut right_node = right(&right_limits); +    let right_node = right(&right_limits);      let right_size = right_node.size();      let (left_y, right_y) = if left_size.height > right_size.height { @@ -85,14 +85,106 @@ pub fn next_to_each_other(          ((right_size.height - left_size.height) / 2.0, 0.0)      }; -    left_node.move_to(Point::new(0.0, left_y)); -    right_node.move_to(Point::new(left_size.width + spacing, right_y)); -      Node::with_children(          Size::new(              left_size.width + spacing + right_size.width,              left_size.height.max(right_size.height),          ), -        vec![left_node, right_node], +        vec![ +            left_node.move_to(Point::new(0.0, left_y)), +            right_node.move_to(Point::new(left_size.width + spacing, right_y)), +        ], +    ) +} + +/// Computes the resulting [`Node`] that fits the [`Limits`] given +/// some width and height requirements and no intrinsic size. +pub fn atomic( +    limits: &Limits, +    width: impl Into<Length>, +    height: impl Into<Length>, +) -> Node { +    let width = width.into(); +    let height = height.into(); + +    Node::new(limits.resolve(width, height, Size::ZERO)) +} + +/// Computes the resulting [`Node`] that fits the [`Limits`] given +/// some width and height requirements and a closure that produces +/// the intrinsic [`Size`] inside the given [`Limits`]. +pub fn sized( +    limits: &Limits, +    width: impl Into<Length>, +    height: impl Into<Length>, +    f: impl FnOnce(&Limits) -> Size, +) -> Node { +    let width = width.into(); +    let height = height.into(); + +    let limits = limits.width(width).height(height); +    let intrinsic_size = f(&limits); + +    Node::new(limits.resolve(width, height, intrinsic_size)) +} + +/// Computes the resulting [`Node`] that fits the [`Limits`] given +/// some width and height requirements and a closure that produces +/// the content [`Node`] inside the given [`Limits`]. +pub fn contained( +    limits: &Limits, +    width: impl Into<Length>, +    height: impl Into<Length>, +    f: impl FnOnce(&Limits) -> Node, +) -> Node { +    let width = width.into(); +    let height = height.into(); + +    let limits = limits.width(width).height(height); +    let content = f(&limits); + +    Node::with_children( +        limits.resolve(width, height, content.size()), +        vec![content], +    ) +} + +/// Computes the [`Node`] that fits the [`Limits`] given some width, height, and +/// [`Padding`] requirements and a closure that produces the content [`Node`] +/// inside the given [`Limits`]. +pub fn padded( +    limits: &Limits, +    width: impl Into<Length>, +    height: impl Into<Length>, +    padding: impl Into<Padding>, +    layout: impl FnOnce(&Limits) -> Node, +) -> Node { +    positioned(limits, width, height, padding, layout, |content, _| content) +} + +/// Computes a [`padded`] [`Node`] with a positioning step. +pub fn positioned( +    limits: &Limits, +    width: impl Into<Length>, +    height: impl Into<Length>, +    padding: impl Into<Padding>, +    layout: impl FnOnce(&Limits) -> Node, +    position: impl FnOnce(Node, Size) -> Node, +) -> Node { +    let width = width.into(); +    let height = height.into(); +    let padding = padding.into(); + +    let limits = limits.width(width).height(height); +    let content = layout(&limits.shrink(padding)); +    let padding = padding.fit(content.size(), limits.max()); + +    let size = limits +        .shrink(padding) +        .resolve(width, height, content.size()); + +    Node::with_children( +        size.expand(padding), +        vec![position(content.move_to((padding.left, padding.top)), size)],      )  } diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index c02b63d8..3358ef3d 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -20,7 +20,7 @@ use crate::Element;  use crate::layout::{Limits, Node};  use crate::widget; -use crate::{Alignment, Padding, Point, Size}; +use crate::{Alignment, Length, Padding, Point, Size};  /// The main axis of a flex layout.  #[derive(Debug)] @@ -47,7 +47,7 @@ impl Axis {          }      } -    fn pack(&self, main: f32, cross: f32) -> (f32, f32) { +    fn pack<T>(&self, main: T, cross: T) -> (T, T) {          match self {              Axis::Horizontal => (main, cross),              Axis::Vertical => (cross, main), @@ -63,6 +63,8 @@ pub fn resolve<Message, Renderer>(      axis: Axis,      renderer: &Renderer,      limits: &Limits, +    width: Length, +    height: Length,      padding: Padding,      spacing: f32,      align_items: Alignment, @@ -72,26 +74,64 @@ pub fn resolve<Message, Renderer>(  where      Renderer: crate::Renderer,  { -    let limits = limits.pad(padding); +    let limits = limits.width(width).height(height).shrink(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 fill_main_sum = 0; +    let mut cross = match axis { +        Axis::Horizontal => match height { +            Length::Shrink => 0.0, +            _ => max_cross, +        }, +        Axis::Vertical => match width { +            Length::Shrink => 0.0, +            _ => max_cross, +        }, +    }; +      let mut available = axis.main(limits.max()) - total_spacing;      let mut nodes: Vec<Node> = Vec::with_capacity(items.len());      nodes.resize(items.len(), Node::default());      for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { -        let fill_factor = match axis { -            Axis::Horizontal => child.as_widget().width(), -            Axis::Vertical => child.as_widget().height(), +        let (fill_main_factor, fill_cross_factor) = { +            let size = child.as_widget().size(); + +            axis.pack(size.width.fill_factor(), size.height.fill_factor()) +        }; + +        if fill_main_factor == 0 { +            if fill_cross_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.as_widget().layout(tree, renderer, &child_limits); +                let size = layout.size(); + +                available -= axis.main(size); +                cross = cross.max(axis.cross(size)); + +                nodes[i] = layout; +            } +        } else { +            fill_main_sum += fill_main_factor;          } -        .fill_factor(); +    } -        if fill_factor == 0 { -            let (max_width, max_height) = axis.pack(available, max_cross); +    for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { +        let (fill_main_factor, fill_cross_factor) = { +            let size = child.as_widget().size(); + +            axis.pack(size.width.fill_factor(), size.height.fill_factor()) +        }; + +        if fill_main_factor == 0 && fill_cross_factor != 0 { +            let (max_width, max_height) = axis.pack(available, cross);              let child_limits =                  Limits::new(Size::ZERO, Size::new(max_width, max_height)); @@ -101,34 +141,47 @@ where              let size = layout.size();              available -= axis.main(size); -            cross = cross.max(axis.cross(size)); +            cross = cross.max(axis.cross(layout.size()));              nodes[i] = layout; -        } else { -            fill_sum += fill_factor;          }      } -    let remaining = available.max(0.0); +    let remaining = match axis { +        Axis::Horizontal => match width { +            Length::Shrink => 0.0, +            _ => available.max(0.0), +        }, +        Axis::Vertical => match height { +            Length::Shrink => 0.0, +            _ => available.max(0.0), +        }, +    };      for (i, (child, tree)) in items.iter().zip(trees).enumerate() { -        let fill_factor = match axis { -            Axis::Horizontal => child.as_widget().width(), -            Axis::Vertical => child.as_widget().height(), -        } -        .fill_factor(); +        let (fill_main_factor, fill_cross_factor) = { +            let size = child.as_widget().size(); + +            axis.pack(size.width.fill_factor(), size.height.fill_factor()) +        }; + +        if fill_main_factor != 0 { +            let max_main = +                remaining * fill_main_factor as f32 / fill_main_sum as f32; -        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_width, min_height) = -                axis.pack(min_main, axis.cross(limits.min())); +            let max_cross = if fill_cross_factor == 0 { +                max_cross +            } else { +                cross +            }; +            let (min_width, min_height) = axis.pack(min_main, 0.0);              let (max_width, max_height) = axis.pack(max_main, max_cross);              let child_limits = Limits::new( @@ -154,18 +207,18 @@ where          let (x, y) = axis.pack(main, pad.1); -        node.move_to(Point::new(x, y)); +        node.move_to_mut(Point::new(x, y));          match axis {              Axis::Horizontal => { -                node.align( +                node.align_mut(                      Alignment::Start,                      align_items,                      Size::new(0.0, cross),                  );              }              Axis::Vertical => { -                node.align( +                node.align_mut(                      align_items,                      Alignment::Start,                      Size::new(cross, 0.0), @@ -178,8 +231,12 @@ where          main += axis.main(size);      } -    let (width, height) = axis.pack(main - pad.0, cross); -    let size = limits.resolve(Size::new(width, height)); +    let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross); +    let size = limits.resolve( +        width, +        height, +        Size::new(intrinsic_width, intrinsic_height), +    ); -    Node::with_children(size.pad(padding), nodes) +    Node::with_children(size.expand(padding), nodes)  } diff --git a/core/src/layout/limits.rs b/core/src/layout/limits.rs index 39a3d98b..7fbc7b9d 100644 --- a/core/src/layout/limits.rs +++ b/core/src/layout/limits.rs @@ -1,12 +1,11 @@  #![allow(clippy::manual_clamp)] -use crate::{Length, Padding, Size}; +use crate::{Length, Size};  /// A set of size constraints for layouting.  #[derive(Debug, Clone, Copy, PartialEq)]  pub struct Limits {      min: Size,      max: Size, -    fill: Size,  }  impl Limits { @@ -14,16 +13,11 @@ impl 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`].      pub const fn new(min: Size, max: Size) -> Limits { -        Limits { -            min, -            max, -            fill: Size::INFINITY, -        } +        Limits { min, max }      }      /// Returns the minimum [`Size`] of the [`Limits`]. @@ -36,26 +30,15 @@ impl Limits {          self.max      } -    /// Returns the fill [`Size`] of the [`Limits`]. -    pub fn fill(&self) -> Size { -        self.fill -    } -      /// Applies a width constraint to the current [`Limits`].      pub fn width(mut self, width: impl Into<Length>) -> Limits {          match width.into() { -            Length::Shrink => { -                self.fill.width = self.min.width; -            } -            Length::Fill | Length::FillPortion(_) => { -                self.fill.width = self.fill.width.min(self.max.width); -            } +            Length::Shrink | Length::Fill | Length::FillPortion(_) => {}              Length::Fixed(amount) => {                  let new_width = amount.min(self.max.width).max(self.min.width);                  self.min.width = new_width;                  self.max.width = new_width; -                self.fill.width = new_width;              }          } @@ -65,19 +48,13 @@ impl Limits {      /// Applies a height constraint to the current [`Limits`].      pub fn height(mut self, height: impl Into<Length>) -> Limits {          match height.into() { -            Length::Shrink => { -                self.fill.height = self.min.height; -            } -            Length::Fill | Length::FillPortion(_) => { -                self.fill.height = self.fill.height.min(self.max.height); -            } +            Length::Shrink | Length::Fill | Length::FillPortion(_) => {}              Length::Fixed(amount) => {                  let new_height =                      amount.min(self.max.height).max(self.min.height);                  self.min.height = new_height;                  self.max.height = new_height; -                self.fill.height = new_height;              }          } @@ -112,13 +89,10 @@ impl Limits {          self      } -    /// Shrinks the current [`Limits`] to account for the given padding. -    pub fn pad(&self, padding: Padding) -> Limits { -        self.shrink(Size::new(padding.horizontal(), padding.vertical())) -    } -      /// Shrinks the current [`Limits`] by the given [`Size`]. -    pub fn shrink(&self, size: Size) -> Limits { +    pub fn shrink(&self, size: impl Into<Size>) -> Limits { +        let size = size.into(); +          let min = Size::new(              (self.min().width - size.width).max(0.0),              (self.min().height - size.height).max(0.0), @@ -129,12 +103,7 @@ impl Limits {              (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 } +        Limits { min, max }      }      /// Removes the minimum width constraint for the current [`Limits`]. @@ -142,22 +111,39 @@ impl 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. -    pub fn resolve(&self, intrinsic_size: Size) -> Size { -        Size::new( -            intrinsic_size -                .width -                .min(self.max.width) -                .max(self.fill.width), -            intrinsic_size +    /// Computes the resulting [`Size`] that fits the [`Limits`] given +    /// some width and height requirements and the intrinsic size of +    /// some content. +    pub fn resolve( +        &self, +        width: impl Into<Length>, +        height: impl Into<Length>, +        intrinsic_size: Size, +    ) -> Size { +        let width = match width.into() { +            Length::Fill | Length::FillPortion(_) => self.max.width, +            Length::Fixed(amount) => { +                amount.min(self.max.width).max(self.min.width) +            } +            Length::Shrink => { +                intrinsic_size.width.min(self.max.width).max(self.min.width) +            } +        }; + +        let height = match height.into() { +            Length::Fill | Length::FillPortion(_) => self.max.height, +            Length::Fixed(amount) => { +                amount.min(self.max.height).max(self.min.height) +            } +            Length::Shrink => intrinsic_size                  .height                  .min(self.max.height) -                .max(self.fill.height), -        ) +                .max(self.min.height), +        }; + +        Size::new(width, height)      }  } diff --git a/core/src/layout/node.rs b/core/src/layout/node.rs index 2b44a7d5..5743a9bd 100644 --- a/core/src/layout/node.rs +++ b/core/src/layout/node.rs @@ -1,4 +1,4 @@ -use crate::{Alignment, Point, Rectangle, Size, Vector}; +use crate::{Alignment, Padding, Point, Rectangle, Size, Vector};  /// The bounds of an element and its children.  #[derive(Debug, Clone, Default)] @@ -26,6 +26,14 @@ impl Node {          }      } +    /// Creates a new [`Node`] that wraps a single child with some [`Padding`]. +    pub fn container(child: Self, padding: Padding) -> Self { +        Self::with_children( +            child.bounds.size().expand(padding), +            vec![child.move_to(Point::new(padding.left, padding.top))], +        ) +    } +      /// Returns the [`Size`] of the [`Node`].      pub fn size(&self) -> Size {          Size::new(self.bounds.width, self.bounds.height) @@ -43,6 +51,17 @@ impl Node {      /// Aligns the [`Node`] in the given space.      pub fn align( +        mut self, +        horizontal_alignment: Alignment, +        vertical_alignment: Alignment, +        space: Size, +    ) -> Self { +        self.align_mut(horizontal_alignment, vertical_alignment, space); +        self +    } + +    /// Mutable reference version of [`Self::align`]. +    pub fn align_mut(          &mut self,          horizontal_alignment: Alignment,          vertical_alignment: Alignment, @@ -70,13 +89,23 @@ impl Node {      }      /// Moves the [`Node`] to the given position. -    pub fn move_to(&mut self, position: Point) { +    pub fn move_to(mut self, position: impl Into<Point>) -> Self { +        self.move_to_mut(position); +        self +    } + +    /// Mutable reference version of [`Self::move_to`]. +    pub fn move_to_mut(&mut self, position: impl Into<Point>) { +        let position = position.into(); +          self.bounds.x = position.x;          self.bounds.y = position.y;      }      /// Translates the [`Node`] by the given translation. -    pub fn translate(self, translation: Vector) -> Self { +    pub fn translate(self, translation: impl Into<Vector>) -> Self { +        let translation = translation.into(); +          Self {              bounds: self.bounds + translation,              ..self diff --git a/core/src/length.rs b/core/src/length.rs index 3adb996e..4c139895 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -36,6 +36,24 @@ impl Length {              Length::Fixed(_) => 0,          }      } + +    /// Returns `true` iff the [`Length`] is either [`Length::Fill`] or +    // [`Length::FillPortion`]. +    pub fn is_fill(&self) -> bool { +        self.fill_factor() != 0 +    } + +    /// Returns the "fluid" variant of the [`Length`]. +    /// +    /// Specifically: +    /// - [`Length::Shrink`] if [`Length::Shrink`] or [`Length::Fixed`]. +    /// - [`Length::Fill`] otherwise. +    pub fn fluid(&self) -> Length { +        match self { +            Length::Fill | Length::FillPortion(_) => Length::Fill, +            Length::Shrink | Length::Fixed(_) => Length::Shrink, +        } +    }  }  impl From<Pixels> for Length { diff --git a/core/src/lib.rs b/core/src/lib.rs index 54ea5839..864df6e6 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -75,3 +75,5 @@ pub use size::Size;  pub use text::Text;  pub use vector::Vector;  pub use widget::Widget; + +pub use smol_str::SmolStr; diff --git a/core/src/mouse/button.rs b/core/src/mouse/button.rs index 3eec7f42..a8f90329 100644 --- a/core/src/mouse/button.rs +++ b/core/src/mouse/button.rs @@ -10,6 +10,12 @@ pub enum Button {      /// The middle (wheel) button.      Middle, +    /// The back mouse button. +    Back, + +    /// The forward mouse button. +    Forward, +      /// Some other button.      Other(u16),  } diff --git a/core/src/padding.rs b/core/src/padding.rs index 0b1bba13..a63f6e29 100644 --- a/core/src/padding.rs +++ b/core/src/padding.rs @@ -154,3 +154,9 @@ impl From<[f32; 4]> for Padding {          }      }  } + +impl From<Padding> for Size { +    fn from(padding: Padding) -> Self { +        Self::new(padding.horizontal(), padding.vertical()) +    } +} diff --git a/core/src/point.rs b/core/src/point.rs index ef42852f..cea57518 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -36,20 +36,26 @@ impl<T: Num> Point<T> {      }  } -impl From<[f32; 2]> for Point { -    fn from([x, y]: [f32; 2]) -> Self { +impl<T> From<[T; 2]> for Point<T> +where +    T: Num, +{ +    fn from([x, y]: [T; 2]) -> Self {          Point { x, y }      }  } -impl From<[u16; 2]> for Point<u16> { -    fn from([x, y]: [u16; 2]) -> Self { -        Point::new(x, y) +impl<T> From<(T, T)> for Point<T> +where +    T: Num, +{ +    fn from((x, y): (T, T)) -> Self { +        Self { x, y }      }  } -impl From<Point> for [f32; 2] { -    fn from(point: Point) -> [f32; 2] { +impl<T> From<Point<T>> for [T; 2] { +    fn from(point: Point<T>) -> [T; 2] {          [point.x, point.y]      }  } diff --git a/core/src/size.rs b/core/src/size.rs index 7ef2f602..90e50d13 100644 --- a/core/src/size.rs +++ b/core/src/size.rs @@ -1,4 +1,4 @@ -use crate::{Padding, Vector}; +use crate::Vector;  /// An amount of space in 2 dimensions.  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -26,15 +26,7 @@ impl Size {      /// A [`Size`] with infinite width and height.      pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY); -    /// Increments the [`Size`] to account for the given padding. -    pub fn pad(&self, padding: Padding) -> Self { -        Size { -            width: self.width + padding.horizontal(), -            height: self.height + padding.vertical(), -        } -    } - -    /// Returns the minimum of each component of this size and another +    /// Returns the minimum of each component of this size and another.      pub fn min(self, other: Self) -> Self {          Size {              width: self.width.min(other.width), @@ -42,13 +34,23 @@ impl Size {          }      } -    /// Returns the maximum of each component of this size and another +    /// Returns the maximum of each component of this size and another.      pub fn max(self, other: Self) -> Self {          Size {              width: self.width.max(other.width),              height: self.height.max(other.height),          }      } + +    /// Expands this [`Size`] by the given amount. +    pub fn expand(self, other: impl Into<Size>) -> Self { +        let other = other.into(); + +        Size { +            width: self.width + other.width, +            height: self.height + other.height, +        } +    }  }  impl From<[f32; 2]> for Size { diff --git a/core/src/time.rs b/core/src/time.rs index 9355ae6d..dcfe4e41 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -1,13 +1,4 @@  //! Keep track of time, both in native and web platforms! -#[cfg(target_arch = "wasm32")] -pub use instant::Instant; - -#[cfg(target_arch = "wasm32")] -pub use instant::Duration; - -#[cfg(not(target_arch = "wasm32"))] -pub use std::time::Instant; - -#[cfg(not(target_arch = "wasm32"))] -pub use std::time::Duration; +pub use web_time::Duration; +pub use web_time::Instant; diff --git a/core/src/widget.rs b/core/src/widget.rs index 294d5984..7f5632ae 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -15,7 +15,7 @@ use crate::layout::{self, Layout};  use crate::mouse;  use crate::overlay;  use crate::renderer; -use crate::{Clipboard, Length, Rectangle, Shell}; +use crate::{Clipboard, Length, Rectangle, Shell, Size};  /// A component that displays information and allows interaction.  /// @@ -43,11 +43,16 @@ pub trait Widget<Message, Renderer>  where      Renderer: crate::Renderer,  { -    /// Returns the width of the [`Widget`]. -    fn width(&self) -> Length; +    /// Returns the [`Size`] of the [`Widget`] in lengths. +    fn size(&self) -> Size<Length>; -    /// Returns the height of the [`Widget`]. -    fn height(&self) -> Length; +    /// Returns a [`Size`] hint for laying out the [`Widget`]. +    /// +    /// This hint may be used by some widget containers to adjust their sizing strategy +    /// during construction. +    fn size_hint(&self) -> Size<Length> { +        self.size() +    }      /// Returns the [`layout::Node`] of the [`Widget`].      /// diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index e020b030..4cabc7ce 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -5,7 +5,9 @@ use crate::mouse;  use crate::renderer;  use crate::text::{self, Paragraph};  use crate::widget::tree::{self, Tree}; -use crate::{Color, Element, Layout, Length, Pixels, Point, Rectangle, Widget}; +use crate::{ +    Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Widget, +};  use std::borrow::Cow; @@ -134,12 +136,11 @@ where          tree::State::new(State(Renderer::Paragraph::default()))      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -205,28 +206,27 @@ pub fn layout<Renderer>(  where      Renderer: text::Renderer,  { -    let limits = limits.width(width).height(height); -    let bounds = limits.max(); - -    let size = size.unwrap_or_else(|| renderer.default_size()); -    let font = font.unwrap_or_else(|| renderer.default_font()); - -    let State(ref mut paragraph) = state; - -    paragraph.update(text::Text { -        content, -        bounds, -        size, -        line_height, -        font, -        horizontal_alignment, -        vertical_alignment, -        shaping, -    }); - -    let size = limits.resolve(paragraph.min_bounds()); - -    layout::Node::new(size) +    layout::sized(limits, width, height, |limits| { +        let bounds = limits.max(); + +        let size = size.unwrap_or_else(|| renderer.default_size()); +        let font = font.unwrap_or_else(|| renderer.default_font()); + +        let State(ref mut paragraph) = state; + +        paragraph.update(text::Text { +            content, +            bounds, +            size, +            line_height, +            font, +            horizontal_alignment, +            vertical_alignment, +            shaping, +        }); + +        paragraph.min_bounds() +    })  }  /// Draws text using the same logic as the [`Text`] widget. diff --git a/core/src/window/event.rs b/core/src/window/event.rs index b9ee7aca..a14d127f 100644 --- a/core/src/window/event.rs +++ b/core/src/window/event.rs @@ -58,7 +58,7 @@ pub enum Event {      /// for each file separately.      FileHovered(PathBuf), -    /// A file has beend dropped into the window. +    /// A file has been dropped into the window.      ///      /// When the user drops multiple files at once, this event will be emitted      /// for each file separately. diff --git a/examples/custom_quad/src/main.rs b/examples/custom_quad/src/main.rs index 13b08250..cc9ad528 100644 --- a/examples/custom_quad/src/main.rs +++ b/examples/custom_quad/src/main.rs @@ -26,12 +26,11 @@ mod quad {      where          Renderer: renderer::Renderer,      { -        fn width(&self) -> Length { -            Length::Shrink -        } - -        fn height(&self) -> Length { -            Length::Shrink +        fn size(&self) -> Size<Length> { +            Size { +                width: Length::Shrink, +                height: Length::Shrink, +            }          }          fn layout( diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 32a14cbe..7ffb4cd0 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -33,12 +33,11 @@ mod circle {      where          Renderer: renderer::Renderer,      { -        fn width(&self) -> Length { -            Length::Shrink -        } - -        fn height(&self) -> Length { -            Length::Shrink +        fn size(&self) -> Size<Length> { +            Size { +                width: Length::Shrink, +                height: Length::Shrink, +            }          }          fn layout( diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index a2fcb275..675e9e26 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -73,16 +73,15 @@ impl Application for Example {      }      fn view(&self) -> Element<Message> { -        let downloads = Column::with_children( -            self.downloads.iter().map(Download::view).collect(), -        ) -        .push( -            button("Add another download") -                .on_press(Message::Add) -                .padding(10), -        ) -        .spacing(20) -        .align_items(Alignment::End); +        let downloads = +            Column::with_children(self.downloads.iter().map(Download::view)) +                .push( +                    button("Add another download") +                        .on_press(Message::Add) +                        .padding(10), +                ) +                .spacing(20) +                .align_items(Alignment::End);          container(downloads)              .width(Length::Fill) diff --git a/examples/editor/Cargo.toml b/examples/editor/Cargo.toml index a3f6ea3b..dc885728 100644 --- a/examples/editor/Cargo.toml +++ b/examples/editor/Cargo.toml @@ -12,4 +12,4 @@ iced.features = ["highlighter", "tokio", "debug"]  tokio.workspace = true  tokio.features = ["fs"] -rfd = "0.12" +rfd = "0.13" diff --git a/examples/editor/src/main.rs b/examples/editor/src/main.rs index 03d1e283..bf2aaaa3 100644 --- a/examples/editor/src/main.rs +++ b/examples/editor/src/main.rs @@ -134,8 +134,8 @@ impl Application for Editor {      }      fn subscription(&self) -> Subscription<Message> { -        keyboard::on_key_press(|key_code, modifiers| match key_code { -            keyboard::KeyCode::S if modifiers.command() => { +        keyboard::on_key_press(|key, modifiers| match key.as_ref() { +            keyboard::Key::Character("s") if modifiers.command() => {                  Some(Message::SaveFile)              }              _ => None, diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 334b012d..fc51ac4a 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -82,8 +82,7 @@ impl Application for Events {              self.last                  .iter()                  .map(|event| text(format!("{event:?}")).size(40)) -                .map(Element::from) -                .collect(), +                .map(Element::from),          );          let toggle = checkbox( diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 96840143..56f7afd5 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -146,7 +146,8 @@ impl Application for GameOfLife {                  .view()                  .map(move |message| Message::Grid(message, version)),              controls, -        ]; +        ] +        .height(Length::Fill);          container(content)              .width(Length::Fill) @@ -178,7 +179,6 @@ fn view_controls<'a>(          slider(1.0..=1000.0, speed as f32, Message::SpeedChanged),          text(format!("x{speed}")).size(16),      ] -    .width(Length::Fill)      .align_items(Alignment::Center)      .spacing(10); diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 8ab3b493..5cf9963d 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -16,12 +16,11 @@ mod rainbow {      }      impl<Message> Widget<Message, Renderer> for Rainbow { -        fn width(&self) -> Length { -            Length::Fill -        } - -        fn height(&self) -> Length { -            Length::Shrink +        fn size(&self) -> Size<Length> { +            Size { +                width: Length::Fill, +                height: Length::Shrink, +            }          }          fn layout( @@ -30,9 +29,9 @@ mod rainbow {              _renderer: &Renderer,              limits: &layout::Limits,          ) -> layout::Node { -            let size = limits.width(Length::Fill).resolve(Size::ZERO); +            let width = limits.max().width; -            layout::Node::new(Size::new(size.width, size.width)) +            layout::Node::new(Size::new(width, width))          }          fn draw( diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 4714c397..89a595c1 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -81,32 +81,25 @@ impl Program for Controls {              );          Row::new() -            .width(Length::Fill)              .height(Length::Fill)              .align_items(Alignment::End)              .push( -                Column::new() -                    .width(Length::Fill) -                    .align_items(Alignment::End) -                    .push( -                        Column::new() -                            .padding(10) -                            .spacing(10) -                            .push( -                                Text::new("Background color") -                                    .style(Color::WHITE), -                            ) -                            .push(sliders) -                            .push( -                                Text::new(format!("{background_color:?}")) -                                    .size(14) -                                    .style(Color::WHITE), -                            ) -                            .push( -                                text_input("Placeholder", text) -                                    .on_input(Message::TextChanged), -                            ), -                    ), +                Column::new().align_items(Alignment::End).push( +                    Column::new() +                        .padding(10) +                        .spacing(10) +                        .push(Text::new("Background color").style(Color::WHITE)) +                        .push(sliders) +                        .push( +                            Text::new(format!("{background_color:?}")) +                                .size(14) +                                .style(Color::WHITE), +                        ) +                        .push( +                            text_input("Placeholder", text) +                                .on_input(Message::TextChanged), +                        ), +                ),              )              .into()      } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 276794c8..b0939d68 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -19,8 +19,9 @@ use iced_winit::winit;  use iced_winit::Clipboard;  use winit::{ -    event::{Event, ModifiersState, WindowEvent}, +    event::{Event, WindowEvent},      event_loop::{ControlFlow, EventLoop}, +    keyboard::ModifiersState,  };  #[cfg(target_arch = "wasm32")] @@ -48,7 +49,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {      tracing_subscriber::fmt::init();      // Initialize winit -    let event_loop = EventLoop::new(); +    let event_loop = EventLoop::new()?;      #[cfg(target_arch = "wasm32")]      let window = winit::window::WindowBuilder::new() @@ -160,67 +161,15 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {      );      // Run event loop -    event_loop.run(move |event, _, control_flow| { +    event_loop.run(move |event, window_target| {          // You should change this if you want to render continuosly -        *control_flow = ControlFlow::Wait; +        window_target.set_control_flow(ControlFlow::Wait);          match event { -            Event::WindowEvent { event, .. } => { -                match event { -                    WindowEvent::CursorMoved { position, .. } => { -                        cursor_position = Some(position); -                    } -                    WindowEvent::ModifiersChanged(new_modifiers) => { -                        modifiers = new_modifiers; -                    } -                    WindowEvent::Resized(_) => { -                        resized = true; -                    } -                    WindowEvent::CloseRequested => { -                        *control_flow = ControlFlow::Exit; -                    } -                    _ => {} -                } - -                // Map window event to iced event -                if let Some(event) = iced_winit::conversion::window_event( -                    window::Id::MAIN, -                    &event, -                    window.scale_factor(), -                    modifiers, -                ) { -                    state.queue_event(event); -                } -            } -            Event::MainEventsCleared => { -                // If there are events pending -                if !state.is_queue_empty() { -                    // We update iced -                    let _ = state.update( -                        viewport.logical_size(), -                        cursor_position -                            .map(|p| { -                                conversion::cursor_position( -                                    p, -                                    viewport.scale_factor(), -                                ) -                            }) -                            .map(mouse::Cursor::Available) -                            .unwrap_or(mouse::Cursor::Unavailable), -                        &mut renderer, -                        &Theme::Dark, -                        &renderer::Style { -                            text_color: Color::WHITE, -                        }, -                        &mut clipboard, -                        &mut debug, -                    ); - -                    // and request a redraw -                    window.request_redraw(); -                } -            } -            Event::RedrawRequested(_) => { +            Event::WindowEvent { +                event: WindowEvent::RedrawRequested, +                .. +            } => {                  if resized {                      let size = window.inner_size(); @@ -309,7 +258,60 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {                      },                  }              } +            Event::WindowEvent { event, .. } => { +                match event { +                    WindowEvent::CursorMoved { position, .. } => { +                        cursor_position = Some(position); +                    } +                    WindowEvent::ModifiersChanged(new_modifiers) => { +                        modifiers = new_modifiers.state(); +                    } +                    WindowEvent::Resized(_) => { +                        resized = true; +                    } +                    WindowEvent::CloseRequested => { +                        window_target.exit(); +                    } +                    _ => {} +                } + +                // Map window event to iced event +                if let Some(event) = iced_winit::conversion::window_event( +                    window::Id::MAIN, +                    event, +                    window.scale_factor(), +                    modifiers, +                ) { +                    state.queue_event(event); +                } +            }              _ => {}          } -    }) + +        // If there are events pending +        if !state.is_queue_empty() { +            // We update iced +            let _ = state.update( +                viewport.logical_size(), +                cursor_position +                    .map(|p| { +                        conversion::cursor_position(p, viewport.scale_factor()) +                    }) +                    .map(mouse::Cursor::Available) +                    .unwrap_or(mouse::Cursor::Unavailable), +                &mut renderer, +                &Theme::Dark, +                &renderer::Style { +                    text_color: Color::WHITE, +                }, +                &mut clipboard, +                &mut debug, +            ); + +            // and request a redraw +            window.request_redraw(); +        } +    })?; + +    Ok(())  } diff --git a/examples/layout/Cargo.toml b/examples/layout/Cargo.toml new file mode 100644 index 00000000..855f98d0 --- /dev/null +++ b/examples/layout/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "layout" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["canvas"] } diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs new file mode 100644 index 00000000..6cf0e570 --- /dev/null +++ b/examples/layout/src/main.rs @@ -0,0 +1,371 @@ +use iced::executor; +use iced::keyboard; +use iced::mouse; +use iced::theme; +use iced::widget::{ +    button, canvas, checkbox, column, container, horizontal_space, pick_list, +    row, scrollable, text, vertical_rule, +}; +use iced::{ +    color, Alignment, Application, Color, Command, Element, Font, Length, +    Point, Rectangle, Renderer, Settings, Subscription, Theme, +}; + +pub fn main() -> iced::Result { +    Layout::run(Settings::default()) +} + +#[derive(Debug)] +struct Layout { +    example: Example, +    explain: bool, +    theme: Theme, +} + +#[derive(Debug, Clone)] +enum Message { +    Next, +    Previous, +    ExplainToggled(bool), +    ThemeSelected(Theme), +} + +impl Application for Layout { +    type Message = Message; +    type Theme = Theme; +    type Executor = executor::Default; +    type Flags = (); + +    fn new(_flags: Self::Flags) -> (Self, Command<Message>) { +        ( +            Self { +                example: Example::default(), +                explain: false, +                theme: Theme::Light, +            }, +            Command::none(), +        ) +    } + +    fn title(&self) -> String { +        format!("{} - Layout - Iced", self.example.title) +    } + +    fn update(&mut self, message: Self::Message) -> Command<Message> { +        match message { +            Message::Next => { +                self.example = self.example.next(); +            } +            Message::Previous => { +                self.example = self.example.previous(); +            } +            Message::ExplainToggled(explain) => { +                self.explain = explain; +            } +            Message::ThemeSelected(theme) => { +                self.theme = theme; +            } +        } + +        Command::none() +    } + +    fn subscription(&self) -> Subscription<Message> { +        use keyboard::key; + +        keyboard::on_key_release(|key, _modifiers| match key { +            keyboard::Key::Named(key::Named::ArrowLeft) => { +                Some(Message::Previous) +            } +            keyboard::Key::Named(key::Named::ArrowRight) => Some(Message::Next), +            _ => None, +        }) +    } + +    fn view(&self) -> Element<Message> { +        let header = row![ +            text(self.example.title).size(20).font(Font::MONOSPACE), +            horizontal_space(Length::Fill), +            checkbox("Explain", self.explain, Message::ExplainToggled), +            pick_list( +                Theme::ALL, +                Some(self.theme.clone()), +                Message::ThemeSelected +            ), +        ] +        .spacing(20) +        .align_items(Alignment::Center); + +        let example = container(if self.explain { +            self.example.view().explain(color!(0x0000ff)) +        } else { +            self.example.view() +        }) +        .style(|theme: &Theme| { +            let palette = theme.extended_palette(); + +            container::Appearance::default() +                .with_border(palette.background.strong.color, 4.0) +        }) +        .padding(4) +        .width(Length::Fill) +        .height(Length::Fill) +        .center_x() +        .center_y(); + +        let controls = row([ +            (!self.example.is_first()).then_some( +                button("← Previous") +                    .padding([5, 10]) +                    .on_press(Message::Previous) +                    .into(), +            ), +            Some(horizontal_space(Length::Fill).into()), +            (!self.example.is_last()).then_some( +                button("Next →") +                    .padding([5, 10]) +                    .on_press(Message::Next) +                    .into(), +            ), +        ] +        .into_iter() +        .flatten()); + +        column![header, example, controls] +            .spacing(10) +            .padding(20) +            .into() +    } + +    fn theme(&self) -> Theme { +        self.theme.clone() +    } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Example { +    title: &'static str, +    view: fn() -> Element<'static, Message>, +} + +impl Example { +    const LIST: &'static [Self] = &[ +        Self { +            title: "Centered", +            view: centered, +        }, +        Self { +            title: "Column", +            view: column_, +        }, +        Self { +            title: "Row", +            view: row_, +        }, +        Self { +            title: "Space", +            view: space, +        }, +        Self { +            title: "Application", +            view: application, +        }, +        Self { +            title: "Nested Quotes", +            view: nested_quotes, +        }, +    ]; + +    fn is_first(self) -> bool { +        Self::LIST.first() == Some(&self) +    } + +    fn is_last(self) -> bool { +        Self::LIST.last() == Some(&self) +    } + +    fn previous(self) -> Self { +        let Some(index) = +            Self::LIST.iter().position(|&example| example == self) +        else { +            return self; +        }; + +        Self::LIST +            .get(index.saturating_sub(1)) +            .copied() +            .unwrap_or(self) +    } + +    fn next(self) -> Self { +        let Some(index) = +            Self::LIST.iter().position(|&example| example == self) +        else { +            return self; +        }; + +        Self::LIST.get(index + 1).copied().unwrap_or(self) +    } + +    fn view(&self) -> Element<Message> { +        (self.view)() +    } +} + +impl Default for Example { +    fn default() -> Self { +        Self::LIST[0] +    } +} + +fn centered<'a>() -> Element<'a, Message> { +    container(text("I am centered!").size(50)) +        .width(Length::Fill) +        .height(Length::Fill) +        .center_x() +        .center_y() +        .into() +} + +fn column_<'a>() -> Element<'a, Message> { +    column![ +        "A column can be used to", +        "lay out widgets vertically.", +        square(50), +        square(50), +        square(50), +        "The amount of space between", +        "elements can be configured!", +    ] +    .spacing(40) +    .into() +} + +fn row_<'a>() -> Element<'a, Message> { +    row![ +        "A row works like a column...", +        square(50), +        square(50), +        square(50), +        "but lays out widgets horizontally!", +    ] +    .spacing(40) +    .into() +} + +fn space<'a>() -> Element<'a, Message> { +    row!["Left!", horizontal_space(Length::Fill), "Right!"].into() +} + +fn application<'a>() -> Element<'a, Message> { +    let header = container( +        row![ +            square(40), +            horizontal_space(Length::Fill), +            "Header!", +            horizontal_space(Length::Fill), +            square(40), +        ] +        .padding(10) +        .align_items(Alignment::Center), +    ) +    .style(|theme: &Theme| { +        let palette = theme.extended_palette(); + +        container::Appearance::default() +            .with_border(palette.background.strong.color, 1) +    }); + +    let sidebar = container( +        column!["Sidebar!", square(50), square(50)] +            .spacing(40) +            .padding(10) +            .width(200) +            .align_items(Alignment::Center), +    ) +    .style(theme::Container::Box) +    .height(Length::Fill) +    .center_y(); + +    let content = container( +        scrollable( +            column![ +                "Content!", +                square(400), +                square(200), +                square(400), +                "The end" +            ] +            .spacing(40) +            .align_items(Alignment::Center) +            .width(Length::Fill), +        ) +        .height(Length::Fill), +    ) +    .padding(10); + +    column![header, row![sidebar, content]].into() +} + +fn nested_quotes<'a>() -> Element<'a, Message> { +    (1..5) +        .fold(column![text("Original text")].padding(10), |quotes, i| { +            column![ +                container( +                    row![vertical_rule(2), quotes].height(Length::Shrink) +                ) +                .style(|theme: &Theme| { +                    let palette = theme.extended_palette(); + +                    container::Appearance::default().with_background( +                        if palette.is_dark { +                            Color { +                                a: 0.01, +                                ..Color::WHITE +                            } +                        } else { +                            Color { +                                a: 0.08, +                                ..Color::BLACK +                            } +                        }, +                    ) +                }), +                text(format!("Reply {i}")) +            ] +            .spacing(10) +            .padding(10) +        }) +        .into() +} + +fn square<'a>(size: impl Into<Length> + Copy) -> Element<'a, Message> { +    struct Square; + +    impl canvas::Program<Message> for Square { +        type State = (); + +        fn draw( +            &self, +            _state: &Self::State, +            renderer: &Renderer, +            theme: &Theme, +            bounds: Rectangle, +            _cursor: mouse::Cursor, +        ) -> Vec<canvas::Geometry> { +            let mut frame = canvas::Frame::new(renderer, bounds.size()); + +            let palette = theme.extended_palette(); + +            frame.fill_rectangle( +                Point::ORIGIN, +                bounds.size(), +                palette.background.strong.color, +            ); + +            vec![frame.into_geometry()] +        } +    } + +    canvas(Square).width(size).height(size).into() +} diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 01560598..04df0744 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -178,35 +178,23 @@ impl Sandbox for App {                  }              }); -            column( -                items -                    .into_iter() -                    .map(|item| { -                        let button = button("Delete") -                            .on_press(Message::DeleteItem(item.clone())) -                            .style(theme::Button::Destructive); - -                        row![ -                            text(&item.name) -                                .style(theme::Text::Color(item.color.into())), -                            horizontal_space(Length::Fill), -                            pick_list( -                                Color::ALL, -                                Some(item.color), -                                move |color| { -                                    Message::ItemColorChanged( -                                        item.clone(), -                                        color, -                                    ) -                                } -                            ), -                            button -                        ] -                        .spacing(20) -                        .into() -                    }) -                    .collect(), -            ) +            column(items.into_iter().map(|item| { +                let button = button("Delete") +                    .on_press(Message::DeleteItem(item.clone())) +                    .style(theme::Button::Destructive); + +                row![ +                    text(&item.name) +                        .style(theme::Text::Color(item.color.into())), +                    horizontal_space(Length::Fill), +                    pick_list(Color::ALL, Some(item.color), move |color| { +                        Message::ItemColorChanged(item.clone(), color) +                    }), +                    button +                ] +                .spacing(20) +                .into() +            }))              .spacing(10)          }); diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index dca8046a..2e119979 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -244,12 +244,11 @@ where          tree::State::new(State::default())      } -    fn width(&self) -> Length { -        Length::Fixed(self.size) -    } - -    fn height(&self) -> Length { -        Length::Fixed(self.size) +    fn size(&self) -> Size<Length> { +        Size { +            width: Length::Fixed(self.size), +            height: Length::Fixed(self.size), +        }      }      fn layout( @@ -258,10 +257,7 @@ where          _renderer: &iced::Renderer<Theme>,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.size).height(self.size); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.size, self.size)      }      fn on_event( @@ -275,8 +271,6 @@ where          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle,      ) -> event::Status { -        const FRAME_RATE: u64 = 60; -          let state = tree.state.downcast_mut::<State>();          if let Event::Window(_, window::Event::RedrawRequested(now)) = event { @@ -287,9 +281,7 @@ where              );              state.cache.clear(); -            shell.request_redraw(RedrawRequest::At( -                now + Duration::from_millis(1000 / FRAME_RATE), -            )); +            shell.request_redraw(RedrawRequest::NextFrame);          }          event::Status::Ignored diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index db10bfba..497e0834 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -165,12 +165,11 @@ where          tree::State::new(State::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -179,10 +178,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.width, self.height)      }      fn on_event( @@ -196,16 +192,12 @@ where          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle,      ) -> event::Status { -        const FRAME_RATE: u64 = 60; -          let state = tree.state.downcast_mut::<State>();          if let Event::Window(_, window::Event::RedrawRequested(now)) = event {              *state = state.timed_transition(self.cycle_duration, now); -            shell.request_redraw(RedrawRequest::At( -                now + Duration::from_millis(1000 / FRAME_RATE), -            )); +            shell.request_redraw(RedrawRequest::NextFrame);          }          event::Status::Ignored diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index a78e9590..93a4605e 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -96,15 +96,14 @@ impl Application for LoadingSpinners {          container(              column.push( -                row(vec![ -                    text("Cycle duration:").into(), +                row![ +                    text("Cycle duration:"),                      slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| {                          Message::CycleDurationChanged(x / 100.0)                      }) -                    .width(200.0) -                    .into(), -                    text(format!("{:.2}s", self.cycle_duration)).into(), -                ]) +                    .width(200.0), +                    text(format!("{:.2}s", self.cycle_duration)), +                ]                  .align_items(iced::Alignment::Center)                  .spacing(20.0),              ), diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index acb14372..963c839e 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -1,6 +1,7 @@  use iced::event::{self, Event};  use iced::executor;  use iced::keyboard; +use iced::keyboard::key;  use iced::theme;  use iced::widget::{      self, button, column, container, horizontal_space, pick_list, row, text, @@ -85,8 +86,9 @@ impl Application for App {              }              Message::Event(event) => match event {                  Event::Keyboard(keyboard::Event::KeyPressed { -                    key_code: keyboard::KeyCode::Tab, +                    key: keyboard::Key::Named(key::Named::Tab),                      modifiers, +                    ..                  }) => {                      if modifiers.shift() {                          widget::focus_previous() @@ -95,7 +97,7 @@ impl Application for App {                      }                  }                  Event::Keyboard(keyboard::Event::KeyPressed { -                    key_code: keyboard::KeyCode::Escape, +                    key: keyboard::Key::Named(key::Named::Escape),                      ..                  }) => {                      self.hide_modal(); @@ -281,12 +283,8 @@ mod modal {              tree.diff_children(&[&self.base, &self.modal]);          } -        fn width(&self) -> Length { -            self.base.as_widget().width() -        } - -        fn height(&self) -> Length { -            self.base.as_widget().height() +        fn size(&self) -> Size<Length> { +            self.base.as_widget().size()          }          fn layout( @@ -420,17 +418,14 @@ mod modal {                  .width(Length::Fill)                  .height(Length::Fill); -            let mut child = self +            let child = self                  .content                  .as_widget() -                .layout(self.tree, renderer, &limits); - -            child.align(Alignment::Center, Alignment::Center, limits.max()); - -            let mut node = layout::Node::with_children(self.size, vec![child]); -            node.move_to(position); +                .layout(self.tree, renderer, &limits) +                .align(Alignment::Center, Alignment::Center, limits.max()); -            node +            layout::Node::with_children(self.size, vec![child]) +                .move_to(position)          }          fn on_event( diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index aa3149bb..d5e5bcbe 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -220,23 +220,26 @@ const PANE_ID_COLOR_FOCUSED: Color = Color::from_rgb(      0x47 as f32 / 255.0,  ); -fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> { -    use keyboard::KeyCode; +fn handle_hotkey(key: keyboard::Key) -> Option<Message> { +    use keyboard::key::{self, Key};      use pane_grid::{Axis, Direction}; -    let direction = match key_code { -        KeyCode::Up => Some(Direction::Up), -        KeyCode::Down => Some(Direction::Down), -        KeyCode::Left => Some(Direction::Left), -        KeyCode::Right => Some(Direction::Right), -        _ => None, -    }; +    match key.as_ref() { +        Key::Character("v") => Some(Message::SplitFocused(Axis::Vertical)), +        Key::Character("h") => Some(Message::SplitFocused(Axis::Horizontal)), +        Key::Character("w") => Some(Message::CloseFocused), +        Key::Named(key) => { +            let direction = match key { +                key::Named::ArrowUp => Some(Direction::Up), +                key::Named::ArrowDown => Some(Direction::Down), +                key::Named::ArrowLeft => Some(Direction::Left), +                key::Named::ArrowRight => Some(Direction::Right), +                _ => None, +            }; -    match key_code { -        KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), -        KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), -        KeyCode::W => Some(Message::CloseFocused), -        _ => direction.map(Message::FocusAdjacent), +            direction.map(Message::FocusAdjacent) +        } +        _ => None,      }  } @@ -297,7 +300,6 @@ fn view_content<'a>(          text(format!("{}x{}", size.width, size.height)).size(24),          controls,      ] -    .width(Length::Fill)      .spacing(10)      .align_items(Alignment::Center); diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs index 21200621..e4d96dc8 100644 --- a/examples/pick_list/src/main.rs +++ b/examples/pick_list/src/main.rs @@ -1,4 +1,4 @@ -use iced::widget::{column, container, pick_list, scrollable, vertical_space}; +use iced::widget::{column, pick_list, scrollable, vertical_space};  use iced::{Alignment, Element, Length, Sandbox, Settings};  pub fn main() -> iced::Result { @@ -52,12 +52,7 @@ impl Sandbox for Example {          .align_items(Alignment::Center)          .spacing(10); -        container(scrollable(content)) -            .width(Length::Fill) -            .height(Length::Fill) -            .center_x() -            .center_y() -            .into() +        scrollable(content).into()      }  } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 20d34be6..6955551e 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -1,11 +1,13 @@ -use iced::keyboard::KeyCode; -use iced::theme::{Button, Container}; +use iced::alignment; +use iced::executor; +use iced::keyboard; +use iced::theme;  use iced::widget::{button, column, container, image, row, text, text_input}; +use iced::window;  use iced::window::screenshot::{self, Screenshot}; -use iced::{alignment, window};  use iced::{ -    event, executor, keyboard, Alignment, Application, Command, ContentFit, -    Element, Event, Length, Rectangle, Renderer, Subscription, Theme, +    Alignment, Application, Command, ContentFit, Element, Length, Rectangle, +    Renderer, Subscription, Theme,  };  use ::image as img; @@ -147,7 +149,7 @@ impl Application for Example {          let image = container(image)              .padding(10) -            .style(Container::Box) +            .style(theme::Container::Box)              .width(Length::FillPortion(2))              .height(Length::Fill)              .center_x() @@ -202,9 +204,10 @@ impl Application for Example {                          self.screenshot.is_some().then(|| Message::Png),                      )                  } else { -                    button(centered_text("Saving...")).style(Button::Secondary) +                    button(centered_text("Saving...")) +                        .style(theme::Button::Secondary)                  } -                .style(Button::Secondary) +                .style(theme::Button::Secondary)                  .padding([10, 20, 10, 20])                  .width(Length::Fill)              ] @@ -213,7 +216,7 @@ impl Application for Example {                  crop_controls,                  button(centered_text("Crop"))                      .on_press(Message::Crop) -                    .style(Button::Destructive) +                    .style(theme::Button::Destructive)                      .padding([10, 20, 10, 20])                      .width(Length::Fill),              ] @@ -256,16 +259,10 @@ impl Application for Example {      }      fn subscription(&self) -> Subscription<Self::Message> { -        event::listen_with(|event, status| { -            if let event::Status::Captured = status { -                return None; -            } +        use keyboard::key; -            if let Event::Keyboard(keyboard::Event::KeyPressed { -                key_code: KeyCode::F5, -                .. -            }) = event -            { +        keyboard::on_key_press(|key, _modifiers| { +            if let keyboard::Key::Named(key::Named::F5) = key {                  Some(Message::Screenshot)              } else {                  None diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index d82ea841..4b57a5a4 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -147,63 +147,54 @@ impl Application for ScrollableDemo {              text("Scroller width:"),              scroller_width_slider,          ] -        .spacing(10) -        .width(Length::Fill); +        .spacing(10); -        let scroll_orientation_controls = column(vec![ -            text("Scrollbar direction:").into(), +        let scroll_orientation_controls = column![ +            text("Scrollbar direction:"),              radio(                  "Vertical",                  Direction::Vertical,                  Some(self.scrollable_direction),                  Message::SwitchDirection, -            ) -            .into(), +            ),              radio(                  "Horizontal",                  Direction::Horizontal,                  Some(self.scrollable_direction),                  Message::SwitchDirection, -            ) -            .into(), +            ),              radio(                  "Both!",                  Direction::Multi,                  Some(self.scrollable_direction),                  Message::SwitchDirection, -            ) -            .into(), -        ]) -        .spacing(10) -        .width(Length::Fill); +            ), +        ] +        .spacing(10); -        let scroll_alignment_controls = column(vec![ -            text("Scrollable alignment:").into(), +        let scroll_alignment_controls = column![ +            text("Scrollable alignment:"),              radio(                  "Start",                  scrollable::Alignment::Start,                  Some(self.alignment),                  Message::AlignmentChanged, -            ) -            .into(), +            ),              radio(                  "End",                  scrollable::Alignment::End,                  Some(self.alignment),                  Message::AlignmentChanged,              ) -            .into(), -        ]) -        .spacing(10) -        .width(Length::Fill); +        ] +        .spacing(10);          let scroll_controls = row![              scroll_slider_controls,              scroll_orientation_controls,              scroll_alignment_controls          ] -        .spacing(20) -        .width(Length::Fill); +        .spacing(20);          let scroll_to_end_button = || {              button("Scroll to end") @@ -229,11 +220,11 @@ impl Application for ScrollableDemo {                          text("End!"),                          scroll_to_beginning_button(),                      ] -                    .width(Length::Fill)                      .align_items(Alignment::Center)                      .padding([40, 0, 40, 0])                      .spacing(40),                  ) +                .width(Length::Fill)                  .height(Length::Fill)                  .direction(scrollable::Direction::Vertical(                      Properties::new() @@ -259,6 +250,7 @@ impl Application for ScrollableDemo {                      .padding([0, 40, 0, 40])                      .spacing(40),                  ) +                .width(Length::Fill)                  .height(Length::Fill)                  .direction(scrollable::Direction::Horizontal(                      Properties::new() @@ -301,6 +293,7 @@ impl Application for ScrollableDemo {                      .padding([0, 40, 0, 40])                      .spacing(40),                  ) +                .width(Length::Fill)                  .height(Length::Fill)                  .direction({                      let properties = Properties::new() @@ -341,20 +334,11 @@ impl Application for ScrollableDemo {          let content: Element<Message> =              column![scroll_controls, scrollable_content, progress_bars] -                .width(Length::Fill) -                .height(Length::Fill)                  .align_items(Alignment::Center)                  .spacing(10)                  .into(); -        Element::from( -            container(content) -                .width(Length::Fill) -                .height(Length::Fill) -                .padding(40) -                .center_x() -                .center_y(), -        ) +        container(content).padding(20).center_x().center_y().into()      }      fn theme(&self) -> Self::Theme { diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index ef935c33..01a114bb 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -79,12 +79,10 @@ impl Application for SierpinskiEmulator {              row![                  text(format!("Iteration: {:?}", self.graph.iteration)),                  slider(0..=10000, self.graph.iteration, Message::IterationSet) -                    .width(Length::Fill)              ]              .padding(10)              .spacing(20),          ] -        .width(Length::Fill)          .align_items(iced::Alignment::Center)          .into()      } diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 0b0f0607..8a0674c1 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -86,12 +86,16 @@ impl Application for Stopwatch {          };          fn handle_hotkey( -            key_code: keyboard::KeyCode, +            key: keyboard::Key,              _modifiers: keyboard::Modifiers,          ) -> Option<Message> { -            match key_code { -                keyboard::KeyCode::Space => Some(Message::Toggle), -                keyboard::KeyCode::R => Some(Message::Reset), +            use keyboard::key; + +            match key.as_ref() { +                keyboard::Key::Named(key::Named::Space) => { +                    Some(Message::Toggle) +                } +                keyboard::Key::Character("r") => Some(Message::Reset),                  _ => None,              }          } diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 51538ec2..10f3c79d 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -53,13 +53,16 @@ impl Sandbox for Styling {                  self.theme = match theme {                      ThemeType::Light => Theme::Light,                      ThemeType::Dark => Theme::Dark, -                    ThemeType::Custom => Theme::custom(theme::Palette { -                        background: Color::from_rgb(1.0, 0.9, 1.0), -                        text: Color::BLACK, -                        primary: Color::from_rgb(0.5, 0.5, 0.0), -                        success: Color::from_rgb(0.0, 1.0, 0.0), -                        danger: Color::from_rgb(1.0, 0.0, 0.0), -                    }), +                    ThemeType::Custom => Theme::custom( +                        String::from("Custom"), +                        theme::Palette { +                            background: Color::from_rgb(1.0, 0.9, 1.0), +                            text: Color::BLACK, +                            primary: Color::from_rgb(0.5, 0.5, 0.0), +                            success: Color::from_rgb(0.0, 1.0, 0.0), +                            danger: Color::from_rgb(1.0, 0.0, 0.0), +                        }, +                    ),                  }              }              Message::InputChanged(value) => self.input_value = value, @@ -104,10 +107,11 @@ impl Sandbox for Styling {          let progress_bar = progress_bar(0.0..=100.0, self.slider_value); -        let scrollable = scrollable( -            column!["Scroll me!", vertical_space(800), "You did it!"] -                .width(Length::Fill), -        ) +        let scrollable = scrollable(column![ +            "Scroll me!", +            vertical_space(800), +            "You did it!" +        ])          .width(Length::Fill)          .height(100); diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 4dc92416..3bf4960f 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -63,7 +63,6 @@ impl Sandbox for Tiger {                  container(apply_color_filter).width(Length::Fill).center_x()              ]              .spacing(20) -            .width(Length::Fill)              .height(Length::Fill),          )          .width(Length::Fill) diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 31b6f191..2e837fa3 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -1,6 +1,7 @@  use iced::event::{self, Event};  use iced::executor;  use iced::keyboard; +use iced::keyboard::key;  use iced::widget::{      self, button, column, container, pick_list, row, slider, text, text_input,  }; @@ -93,11 +94,12 @@ impl Application for App {                  Command::none()              }              Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { -                key_code: keyboard::KeyCode::Tab, +                key: keyboard::Key::Named(key::Named::Tab),                  modifiers, +                ..              })) if modifiers.shift() => widget::focus_previous(),              Message::Event(Event::Keyboard(keyboard::Event::KeyPressed { -                key_code: keyboard::KeyCode::Tab, +                key: keyboard::Key::Named(key::Named::Tab),                  ..              })) => widget::focus_next(),              Message::Event(_) => Command::none(), @@ -106,9 +108,7 @@ impl Application for App {      fn view<'a>(&'a self) -> Element<'a, Message> {          let subtitle = |title, content: Element<'a, Message>| { -            column![text(title).size(14), content] -                .width(Length::Fill) -                .spacing(5) +            column![text(title).size(14), content].spacing(5)          };          let mut add_toast = button("Add Toast"); @@ -153,14 +153,11 @@ impl Application for App {                              Message::Timeout                          )                          .step(1.0) -                        .width(Length::Fill)                      ]                      .spacing(5)                      .into()                  ), -                column![add_toast] -                    .width(Length::Fill) -                    .align_items(Alignment::End) +                column![add_toast].align_items(Alignment::End)              ]              .spacing(10)              .max_width(200), @@ -318,12 +315,8 @@ mod toast {      }      impl<'a, Message> Widget<Message, Renderer> for Manager<'a, Message> { -        fn width(&self) -> Length { -            self.content.as_widget().width() -        } - -        fn height(&self) -> Length { -            self.content.as_widget().height() +        fn size(&self) -> Size<Length> { +            self.content.as_widget().size()          }          fn layout( @@ -513,14 +506,14 @@ mod toast {              position: Point,              _translation: Vector,          ) -> layout::Node { -            let limits = layout::Limits::new(Size::ZERO, bounds) -                .width(Length::Fill) -                .height(Length::Fill); +            let limits = layout::Limits::new(Size::ZERO, bounds);              layout::flex::resolve(                  layout::flex::Axis::Vertical,                  renderer,                  &limits, +                Length::Fill, +                Length::Fill,                  10.into(),                  10.0,                  Alignment::End, diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 4dac032c..3d79f087 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -254,27 +254,27 @@ impl Application for Todos {                      .spacing(20)                      .max_width(800); -                scrollable( -                    container(content) -                        .width(Length::Fill) -                        .padding(40) -                        .center_x(), -                ) -                .into() +                scrollable(container(content).padding(40).center_x()).into()              }          }      }      fn subscription(&self) -> Subscription<Message> { -        keyboard::on_key_press(|key_code, modifiers| { -            match (key_code, modifiers) { -                (keyboard::KeyCode::Tab, _) => Some(Message::TabPressed { +        use keyboard::key; + +        keyboard::on_key_press(|key, modifiers| { +            let keyboard::Key::Named(key) = key else { +                return None; +            }; + +            match (key, modifiers) { +                (key::Named::Tab, _) => Some(Message::TabPressed {                      shift: modifiers.shift(),                  }), -                (keyboard::KeyCode::Up, keyboard::Modifiers::SHIFT) => { +                (key::Named::ArrowUp, keyboard::Modifiers::SHIFT) => {                      Some(Message::ToggleFullscreen(window::Mode::Fullscreen))                  } -                (keyboard::KeyCode::Down, keyboard::Modifiers::SHIFT) => { +                (key::Named::ArrowDown, keyboard::Modifiers::SHIFT) => {                      Some(Message::ToggleFullscreen(window::Mode::Windowed))                  }                  _ => None, @@ -472,7 +472,6 @@ fn empty_message(message: &str) -> Element<'_, Message> {              .horizontal_alignment(alignment::Horizontal::Center)              .style(Color::from([0.7, 0.7, 0.7])),      ) -    .width(Length::Fill)      .height(200)      .center_y()      .into() diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 7003d8ae..8633bc0a 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -509,7 +509,6 @@ impl<'a> Step {                          )                      })                      .map(Element::from) -                    .collect()              )              .spacing(10)          ] @@ -692,11 +691,7 @@ fn ferris<'a>(  }  fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> { -    iced::widget::button( -        text(label).horizontal_alignment(alignment::Horizontal::Center), -    ) -    .padding(12) -    .width(100) +    iced::widget::button(text(label)).padding([12, 24])  }  fn color_slider<'a>( diff --git a/examples/vectorial_text/Cargo.toml b/examples/vectorial_text/Cargo.toml new file mode 100644 index 00000000..76c1af7c --- /dev/null +++ b/examples/vectorial_text/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "vectorial_text" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["canvas", "debug"] } diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs new file mode 100644 index 00000000..d366b907 --- /dev/null +++ b/examples/vectorial_text/src/main.rs @@ -0,0 +1,175 @@ +use iced::alignment::{self, Alignment}; +use iced::mouse; +use iced::widget::{ +    canvas, checkbox, column, horizontal_space, row, slider, text, +}; +use iced::{ +    Element, Length, Point, Rectangle, Renderer, Sandbox, Settings, Theme, +    Vector, +}; + +pub fn main() -> iced::Result { +    VectorialText::run(Settings { +        antialiasing: true, +        ..Settings::default() +    }) +} + +struct VectorialText { +    state: State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { +    SizeChanged(f32), +    AngleChanged(f32), +    ScaleChanged(f32), +    ToggleJapanese(bool), +} + +impl Sandbox for VectorialText { +    type Message = Message; + +    fn new() -> Self { +        Self { +            state: State::new(), +        } +    } + +    fn title(&self) -> String { +        String::from("Vectorial Text - Iced") +    } + +    fn update(&mut self, message: Message) { +        match message { +            Message::SizeChanged(size) => { +                self.state.size = size; +            } +            Message::AngleChanged(angle) => { +                self.state.angle = angle; +            } +            Message::ScaleChanged(scale) => { +                self.state.scale = scale; +            } +            Message::ToggleJapanese(use_japanese) => { +                self.state.use_japanese = use_japanese; +            } +        } + +        self.state.cache.clear(); +    } + +    fn view(&self) -> Element<Message> { +        let slider_with_label = |label, range, value, message: fn(f32) -> _| { +            column![ +                row![ +                    text(label), +                    horizontal_space(Length::Fill), +                    text(format!("{:.2}", value)) +                ], +                slider(range, value, message).step(0.01) +            ] +            .spacing(2) +        }; + +        column![ +            canvas(&self.state).width(Length::Fill).height(Length::Fill), +            column![ +                checkbox( +                    "Use Japanese", +                    self.state.use_japanese, +                    Message::ToggleJapanese +                ), +                row![ +                    slider_with_label( +                        "Size", +                        2.0..=80.0, +                        self.state.size, +                        Message::SizeChanged, +                    ), +                    slider_with_label( +                        "Angle", +                        0.0..=360.0, +                        self.state.angle, +                        Message::AngleChanged, +                    ), +                    slider_with_label( +                        "Scale", +                        1.0..=20.0, +                        self.state.scale, +                        Message::ScaleChanged, +                    ), +                ] +                .spacing(20), +            ] +            .align_items(Alignment::Center) +            .spacing(10) +        ] +        .spacing(10) +        .padding(20) +        .into() +    } + +    fn theme(&self) -> Theme { +        Theme::Dark +    } +} + +struct State { +    size: f32, +    angle: f32, +    scale: f32, +    use_japanese: bool, +    cache: canvas::Cache, +} + +impl State { +    pub fn new() -> Self { +        Self { +            size: 40.0, +            angle: 0.0, +            scale: 1.0, +            use_japanese: false, +            cache: canvas::Cache::new(), +        } +    } +} + +impl<Message> canvas::Program<Message> for State { +    type State = (); + +    fn draw( +        &self, +        _state: &Self::State, +        renderer: &Renderer, +        theme: &Theme, +        bounds: Rectangle, +        _cursor: mouse::Cursor, +    ) -> Vec<canvas::Geometry> { +        let geometry = self.cache.draw(renderer, bounds.size(), |frame| { +            let palette = theme.palette(); +            let center = bounds.center(); + +            frame.translate(Vector::new(center.x, center.y)); +            frame.scale(self.scale); +            frame.rotate(self.angle * std::f32::consts::PI / 180.0); + +            frame.fill_text(canvas::Text { +                position: Point::new(0.0, 0.0), +                color: palette.text, +                size: self.size.into(), +                content: String::from(if self.use_japanese { +                    "ベクトルテキスト🎉" +                } else { +                    "Vectorial Text! 🎉" +                }), +                horizontal_alignment: alignment::Horizontal::Center, +                vertical_alignment: alignment::Vertical::Center, +                shaping: text::Shaping::Advanced, +                ..canvas::Text::default() +            }); +        }); + +        vec![geometry] +    } +} diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 920189f5..38a6db1e 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -3,7 +3,7 @@ mod echo;  use iced::alignment::{self, Alignment};  use iced::executor;  use iced::widget::{ -    button, column, container, row, scrollable, text, text_input, Column, +    button, column, container, row, scrollable, text, text_input,  };  use iced::{      Application, Color, Command, Element, Length, Settings, Subscription, Theme, @@ -108,15 +108,9 @@ impl Application for WebSocket {              .into()          } else {              scrollable( -                Column::with_children( -                    self.messages -                        .iter() -                        .cloned() -                        .map(text) -                        .map(Element::from) -                        .collect(), +                column( +                    self.messages.iter().cloned().map(text).map(Element::from),                  ) -                .width(Length::Fill)                  .spacing(10),              )              .id(MESSAGE_LOG.clone()) @@ -131,7 +125,7 @@ impl Application for WebSocket {              let mut button = button(                  text("Send") -                    .height(Length::Fill) +                    .height(40)                      .vertical_alignment(alignment::Vertical::Center),              )              .padding([0, 20]); @@ -149,7 +143,6 @@ impl Application for WebSocket {          };          column![message_log, new_message_input] -            .width(Length::Fill)              .height(Length::Fill)              .padding(20)              .spacing(10) diff --git a/futures/src/keyboard.rs b/futures/src/keyboard.rs index af68e1f2..8e7da38f 100644 --- a/futures/src/keyboard.rs +++ b/futures/src/keyboard.rs @@ -1,6 +1,6 @@  //! Listen to keyboard events.  use crate::core; -use crate::core::keyboard::{Event, KeyCode, Modifiers}; +use crate::core::keyboard::{Event, Key, Modifiers};  use crate::subscription::{self, Subscription};  use crate::MaybeSend; @@ -10,7 +10,7 @@ use crate::MaybeSend;  /// If the function returns `None`, the key press will be simply  /// ignored.  pub fn on_key_press<Message>( -    f: fn(KeyCode, Modifiers) -> Option<Message>, +    f: fn(Key, Modifiers) -> Option<Message>,  ) -> Subscription<Message>  where      Message: MaybeSend + 'static, @@ -22,11 +22,10 @@ where          match (event, status) {              (                  core::Event::Keyboard(Event::KeyPressed { -                    key_code, -                    modifiers, +                    key, modifiers, ..                  }),                  core::event::Status::Ignored, -            ) => f(key_code, modifiers), +            ) => f(key, modifiers),              _ => None,          }      }) @@ -38,7 +37,7 @@ where  /// If the function returns `None`, the key release will be simply  /// ignored.  pub fn on_key_release<Message>( -    f: fn(KeyCode, Modifiers) -> Option<Message>, +    f: fn(Key, Modifiers) -> Option<Message>,  ) -> Subscription<Message>  where      Message: MaybeSend + 'static, @@ -50,11 +49,12 @@ where          match (event, status) {              (                  core::Event::Keyboard(Event::KeyReleased { -                    key_code, +                    key,                      modifiers, +                    ..                  }),                  core::event::Status::Ignored, -            ) => f(key_code, modifiers), +            ) => f(key, modifiers),              _ => None,          }      }) diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 595cc274..59e9f5b4 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -73,6 +73,11 @@ impl<T: Damage> Damage for Primitive<T> {                  bounds.expand(1.5)              } +            Self::RawText(raw) => { +                // TODO: Add `size` field to `raw` to compute more accurate +                // damage bounds (?) +                raw.clip_bounds.expand(1.5) +            }              Self::Quad { bounds, .. }              | Self::Image { bounds, .. }              | Self::Svg { bounds, .. } => bounds.expand(1.0), diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index 0bf7ec97..d314e85e 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -1,6 +1,8 @@  use crate::core::alignment;  use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Pixels, Point}; +use crate::core::{Color, Font, Pixels, Point, Size, Vector}; +use crate::geometry::Path; +use crate::text;  /// A bunch of text that can be drawn to a canvas  #[derive(Debug, Clone)] @@ -32,6 +34,137 @@ pub struct Text {      pub shaping: Shaping,  } +impl Text { +    /// Computes the [`Path`]s of the [`Text`] and draws them using +    /// the given closure. +    pub fn draw_with(&self, mut f: impl FnMut(Path, Color)) { +        let mut font_system = +            text::font_system().write().expect("Write font system"); + +        let mut buffer = cosmic_text::BufferLine::new( +            &self.content, +            cosmic_text::AttrsList::new(text::to_attributes(self.font)), +            text::to_shaping(self.shaping), +        ); + +        let layout = buffer.layout( +            font_system.raw(), +            self.size.0, +            f32::MAX, +            cosmic_text::Wrap::None, +        ); + +        let translation_x = match self.horizontal_alignment { +            alignment::Horizontal::Left => self.position.x, +            alignment::Horizontal::Center | alignment::Horizontal::Right => { +                let mut line_width = 0.0f32; + +                for line in layout.iter() { +                    line_width = line_width.max(line.w); +                } + +                if self.horizontal_alignment == alignment::Horizontal::Center { +                    self.position.x - line_width / 2.0 +                } else { +                    self.position.x - line_width +                } +            } +        }; + +        let translation_y = { +            let line_height = self.line_height.to_absolute(self.size); + +            match self.vertical_alignment { +                alignment::Vertical::Top => self.position.y, +                alignment::Vertical::Center => { +                    self.position.y - line_height.0 / 2.0 +                } +                alignment::Vertical::Bottom => self.position.y - line_height.0, +            } +        }; + +        let mut swash_cache = cosmic_text::SwashCache::new(); + +        for run in layout.iter() { +            for glyph in run.glyphs.iter() { +                let physical_glyph = glyph.physical((0.0, 0.0), 1.0); + +                let start_x = translation_x + glyph.x + glyph.x_offset; +                let start_y = translation_y + glyph.y_offset + self.size.0; +                let offset = Vector::new(start_x, start_y); + +                if let Some(commands) = swash_cache.get_outline_commands( +                    font_system.raw(), +                    physical_glyph.cache_key, +                ) { +                    let glyph = Path::new(|path| { +                        use cosmic_text::Command; + +                        for command in commands { +                            match command { +                                Command::MoveTo(p) => { +                                    path.move_to( +                                        Point::new(p.x, -p.y) + offset, +                                    ); +                                } +                                Command::LineTo(p) => { +                                    path.line_to( +                                        Point::new(p.x, -p.y) + offset, +                                    ); +                                } +                                Command::CurveTo(control_a, control_b, to) => { +                                    path.bezier_curve_to( +                                        Point::new(control_a.x, -control_a.y) +                                            + offset, +                                        Point::new(control_b.x, -control_b.y) +                                            + offset, +                                        Point::new(to.x, -to.y) + offset, +                                    ); +                                } +                                Command::QuadTo(control, to) => { +                                    path.quadratic_curve_to( +                                        Point::new(control.x, -control.y) +                                            + offset, +                                        Point::new(to.x, -to.y) + offset, +                                    ); +                                } +                                Command::Close => { +                                    path.close(); +                                } +                            } +                        } +                    }); + +                    f(glyph, self.color); +                } else { +                    // TODO: Raster image support for `Canvas` +                    let [r, g, b, a] = self.color.into_rgba8(); + +                    swash_cache.with_pixels( +                        font_system.raw(), +                        physical_glyph.cache_key, +                        cosmic_text::Color::rgba(r, g, b, a), +                        |x, y, color| { +                            f( +                                Path::rectangle( +                                    Point::new(x as f32, y as f32) + offset, +                                    Size::new(1.0, 1.0), +                                ), +                                Color::from_rgba8( +                                    color.r(), +                                    color.g(), +                                    color.b(), +                                    color.a() as f32 / 255.0, +                                ), +                            ); +                        }, +                    ); +                } +            } +        } +    } +} +  impl Default for Text {      fn default() -> Text {          Text { diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index ed75776c..20affaaf 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -57,6 +57,8 @@ pub enum Primitive<T> {          /// The clip bounds of the editor.          clip_bounds: Rectangle,      }, +    /// A raw `cosmic-text` primitive +    RawText(crate::text::Raw),      /// A quad primitive      Quad {          /// The bounds of the quad diff --git a/graphics/src/text.rs b/graphics/src/text.rs index fc7694c2..7c4b5e31 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -9,14 +9,13 @@ pub use paragraph::Paragraph;  pub use cosmic_text; -use crate::color;  use crate::core::font::{self, Font};  use crate::core::text::Shaping; -use crate::core::{Color, Size}; +use crate::core::{Color, Point, Rectangle, Size};  use once_cell::sync::OnceCell;  use std::borrow::Cow; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, RwLock, Weak};  /// Returns the global [`FontSystem`].  pub fn font_system() -> &'static RwLock<FontSystem> { @@ -68,6 +67,29 @@ impl FontSystem {  #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]  pub struct Version(u32); +/// A weak reference to a [`cosmic-text::Buffer`] that can be drawn. +#[derive(Debug, Clone)] +pub struct Raw { +    /// A weak reference to a [`cosmic_text::Buffer`]. +    pub buffer: Weak<cosmic_text::Buffer>, +    /// The position of the text. +    pub position: Point, +    /// The color of the text. +    pub color: Color, +    /// The clip bounds of the text. +    pub clip_bounds: Rectangle, +} + +impl PartialEq for Raw { +    fn eq(&self, _other: &Self) -> bool { +        // TODO: There is no proper way to compare raw buffers +        // For now, no two instances of `Raw` text will be equal. +        // This should be fine, but could trigger unnecessary redraws +        // in the future. +        false +    } +} +  /// Measures the dimensions of the given [`cosmic_text::Buffer`].  pub fn measure(buffer: &cosmic_text::Buffer) -> Size {      let (width, total_lines) = buffer @@ -150,12 +172,7 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {  /// Converts some [`Color`] to a [`cosmic_text::Color`].  pub fn to_color(color: Color) -> cosmic_text::Color { -    let [r, g, b, a] = color::pack(color).components(); +    let [r, g, b, a] = color.into_rgba8(); -    cosmic_text::Color::rgba( -        (r * 255.0) as u8, -        (g * 255.0) as u8, -        (b * 255.0) as u8, -        (a * 255.0) as u8, -    ) +    cosmic_text::Color::rgba(r, g, b, a)  } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 4a08a8f4..5d027542 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -187,38 +187,43 @@ impl core::text::Paragraph for Paragraph {      }      fn grapheme_position(&self, line: usize, index: usize) -> Option<Point> { +        use unicode_segmentation::UnicodeSegmentation; +          let run = self.internal().buffer.layout_runs().nth(line)?;          // index represents a grapheme, not a glyph          // Let's find the first glyph for the given grapheme cluster          let mut last_start = None; +        let mut last_grapheme_count = 0;          let mut graphemes_seen = 0;          let glyph = run              .glyphs              .iter()              .find(|glyph| { -                if graphemes_seen == index { -                    return true; -                } -                  if Some(glyph.start) != last_start { +                    last_grapheme_count = run.text[glyph.start..glyph.end] +                        .graphemes(false) +                        .count();                      last_start = Some(glyph.start); -                    graphemes_seen += 1; +                    graphemes_seen += last_grapheme_count;                  } -                false +                graphemes_seen >= index              })              .or_else(|| run.glyphs.last())?; -        let advance_last = if index == run.glyphs.len() { -            glyph.w -        } else { +        let advance = if index == 0 {              0.0 +        } else { +            glyph.w +                * (1.0 +                    - graphemes_seen.saturating_sub(index) as f32 +                        / last_grapheme_count.max(1) as f32)          };          Some(Point::new( -            glyph.x + glyph.x_offset * glyph.font_size + advance_last, +            glyph.x + glyph.x_offset * glyph.font_size + advance,              glyph.y - glyph.y_offset * glyph.font_size,          ))      } diff --git a/renderer/src/backend.rs b/renderer/src/backend.rs deleted file mode 100644 index 3f229b52..00000000 --- a/renderer/src/backend.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::core::text; -use crate::core::{Font, Point, Size}; -use crate::graphics::backend; - -use std::borrow::Cow; - -#[allow(clippy::large_enum_variant)] -pub enum Backend { -    TinySkia(iced_tiny_skia::Backend), -    #[cfg(feature = "wgpu")] -    Wgpu(iced_wgpu::Backend), -} - -macro_rules! delegate { -    ($backend:expr, $name:ident, $body:expr) => { -        match $backend { -            Self::TinySkia($name) => $body, -            #[cfg(feature = "wgpu")] -            Self::Wgpu($name) => $body, -        } -    }; -} - -impl backend::Text for Backend { -    const ICON_FONT: Font = Font::with_name("Iced-Icons"); -    const CHECKMARK_ICON: char = '\u{f00c}'; -    const ARROW_DOWN_ICON: char = '\u{e800}'; - -    fn default_font(&self) -> Font { -        delegate!(self, backend, backend.default_font()) -    } - -    fn default_size(&self) -> f32 { -        delegate!(self, backend, backend.default_size()) -    } - -    fn measure( -        &self, -        contents: &str, -        size: f32, -        line_height: text::LineHeight, -        font: Font, -        bounds: Size, -        shaping: text::Shaping, -    ) -> Size { -        delegate!( -            self, -            backend, -            backend.measure(contents, size, line_height, font, bounds, shaping) -        ) -    } - -    fn hit_test( -        &self, -        contents: &str, -        size: f32, -        line_height: text::LineHeight, -        font: Font, -        bounds: Size, -        shaping: text::Shaping, -        position: Point, -        nearest_only: bool, -    ) -> Option<text::Hit> { -        delegate!( -            self, -            backend, -            backend.hit_test( -                contents, -                size, -                line_height, -                font, -                bounds, -                shaping, -                position, -                nearest_only, -            ) -        ) -    } - -    fn load_font(&mut self, font: Cow<'static, [u8]>) { -        delegate!(self, backend, backend.load_font(font)); -    } -} - -#[cfg(feature = "image")] -impl backend::Image for Backend { -    fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> { -        delegate!(self, backend, backend.dimensions(handle)) -    } -} - -#[cfg(feature = "svg")] -impl backend::Svg for Backend { -    fn viewport_dimensions( -        &self, -        handle: &crate::core::svg::Handle, -    ) -> Size<u32> { -        delegate!(self, backend, backend.viewport_dimensions(handle)) -    } -} diff --git a/runtime/src/window.rs b/runtime/src/window.rs index f9d943f6..2136d64d 100644 --- a/runtime/src/window.rs +++ b/runtime/src/window.rs @@ -65,11 +65,33 @@ pub fn fetch_size<Message>(      Command::single(command::Action::Window(Action::FetchSize(id, Box::new(f))))  } +/// Fetches if the window is maximized. +pub fn fetch_maximized<Message>( +    id: Id, +    f: impl FnOnce(bool) -> Message + 'static, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::FetchMaximized( +        id, +        Box::new(f), +    ))) +} +  /// Maximizes the window.  pub fn maximize<Message>(id: Id, maximized: bool) -> Command<Message> {      Command::single(command::Action::Window(Action::Maximize(id, maximized)))  } +/// Fetches if the window is minimized. +pub fn fetch_minimized<Message>( +    id: Id, +    f: impl FnOnce(Option<bool>) -> Message + 'static, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::FetchMinimized( +        id, +        Box::new(f), +    ))) +} +  /// Minimizes the window.  pub fn minimize<Message>(id: Id, minimized: bool) -> Command<Message> {      Command::single(command::Action::Window(Action::Minimize(id, minimized))) diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs index 2d98b607..8b532569 100644 --- a/runtime/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -21,8 +21,19 @@ pub enum Action<T> {      Resize(Id, Size),      /// Fetch the current logical dimensions of the window.      FetchSize(Id, Box<dyn FnOnce(Size) -> T + 'static>), +    /// Fetch if the current window is maximized or not. +    /// +    /// ## Platform-specific +    /// - **iOS / Android / Web:** Unsupported. +    FetchMaximized(Id, Box<dyn FnOnce(bool) -> T + 'static>),      /// Set the window to maximized or back      Maximize(Id, bool), +    /// Fetch if the current window is minimized or not. +    /// +    /// ## Platform-specific +    /// - **Wayland:** Always `None`. +    /// - **iOS / Android / Web:** Unsupported. +    FetchMinimized(Id, Box<dyn FnOnce(Option<bool>) -> T + 'static>),      /// Set the window to minimized or back      Minimize(Id, bool),      /// Move the window to the given logical coordinates. @@ -106,7 +117,13 @@ impl<T> Action<T> {              Self::FetchSize(id, o) => {                  Action::FetchSize(id, Box::new(move |s| f(o(s))))              } +            Self::FetchMaximized(id, o) => { +                Action::FetchMaximized(id, Box::new(move |s| f(o(s)))) +            }              Self::Maximize(id, maximized) => Action::Maximize(id, maximized), +            Self::FetchMinimized(id, o) => { +                Action::FetchMinimized(id, Box::new(move |s| f(o(s)))) +            }              Self::Minimize(id, minimized) => Action::Minimize(id, minimized),              Self::Move(id, position) => Action::Move(id, position),              Self::ChangeMode(id, mode) => Action::ChangeMode(id, mode), @@ -144,9 +161,15 @@ impl<T> fmt::Debug for Action<T> {                  write!(f, "Action::Resize({id:?}, {size:?})")              }              Self::FetchSize(id, _) => write!(f, "Action::FetchSize({id:?})"), +            Self::FetchMaximized(id, _) => { +                write!(f, "Action::FetchMaximized({id:?})") +            }              Self::Maximize(id, maximized) => {                  write!(f, "Action::Maximize({id:?}, {maximized})")              } +            Self::FetchMinimized(id, _) => { +                write!(f, "Action::FetchMinimized({id:?})") +            }              Self::Minimize(id, minimized) => {                  write!(f, "Action::Minimize({id:?}, {minimized}")              } @@ -230,7 +230,8 @@ pub mod event {  pub mod keyboard {      //! Listen and react to keyboard events. -    pub use crate::core::keyboard::{Event, KeyCode, Modifiers}; +    pub use crate::core::keyboard::key; +    pub use crate::core::keyboard::{Event, Key, Location, Modifiers};      pub use iced_futures::keyboard::{on_key_press, on_key_release};  } diff --git a/src/time.rs b/src/time.rs index 37d454ed..f10f7a5e 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,4 +1,5 @@  //! Listen and react to time.  pub use iced_core::time::{Duration, Instant}; +#[allow(unused_imports)]  pub use iced_futures::backend::default::time::*; diff --git a/style/src/container.rs b/style/src/container.rs index ec543ae4..490a9dab 100644 --- a/style/src/container.rs +++ b/style/src/container.rs @@ -1,5 +1,5 @@  //! Change the appearance of a container. -use iced_core::{Background, BorderRadius, Color}; +use crate::core::{Background, BorderRadius, Color, Pixels};  /// The appearance of a container.  #[derive(Debug, Clone, Copy)] @@ -16,6 +16,30 @@ pub struct Appearance {      pub border_color: Color,  } +impl Appearance { +    /// Derives a new [`Appearance`] with a border of the given [`Color`] and +    /// `width`. +    pub fn with_border( +        self, +        color: impl Into<Color>, +        width: impl Into<Pixels>, +    ) -> Self { +        Self { +            border_color: color.into(), +            border_width: width.into().0, +            ..self +        } +    } + +    /// Derives a new [`Appearance`] with the given [`Background`]. +    pub fn with_background(self, background: impl Into<Background>) -> Self { +        Self { +            background: Some(background.into()), +            ..self +        } +    } +} +  impl std::default::Default for Appearance {      fn default() -> Self {          Self { diff --git a/style/src/theme.rs b/style/src/theme.rs index 4af07794..f78587e5 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -23,6 +23,7 @@ use crate::toggler;  use iced_core::{Background, Color, Vector}; +use std::fmt;  use std::rc::Rc;  /// A built-in theme. @@ -38,18 +39,22 @@ pub enum Theme {  }  impl Theme { +    /// A list with all the defined themes. +    pub const ALL: &'static [Self] = &[Self::Light, Self::Dark]; +      /// Creates a new custom [`Theme`] from the given [`Palette`]. -    pub fn custom(palette: Palette) -> Self { -        Self::custom_with_fn(palette, palette::Extended::generate) +    pub fn custom(name: String, palette: Palette) -> Self { +        Self::custom_with_fn(name, palette, palette::Extended::generate)      }      /// Creates a new custom [`Theme`] from the given [`Palette`], with      /// a custom generator of a [`palette::Extended`].      pub fn custom_with_fn( +        name: String,          palette: Palette,          generate: impl FnOnce(Palette) -> palette::Extended,      ) -> Self { -        Self::Custom(Box::new(Custom::with_fn(palette, generate))) +        Self::Custom(Box::new(Custom::with_fn(name, palette, generate)))      }      /// Returns the [`Palette`] of the [`Theme`]. @@ -71,32 +76,51 @@ impl Theme {      }  } +impl fmt::Display for Theme { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        match self { +            Self::Light => write!(f, "Light"), +            Self::Dark => write!(f, "Dark"), +            Self::Custom(custom) => custom.fmt(f), +        } +    } +} +  /// A [`Theme`] with a customized [`Palette`]. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)]  pub struct Custom { +    name: String,      palette: Palette,      extended: palette::Extended,  }  impl Custom {      /// Creates a [`Custom`] theme from the given [`Palette`]. -    pub fn new(palette: Palette) -> Self { -        Self::with_fn(palette, palette::Extended::generate) +    pub fn new(name: String, palette: Palette) -> Self { +        Self::with_fn(name, palette, palette::Extended::generate)      }      /// Creates a [`Custom`] theme from the given [`Palette`] with      /// a custom generator of a [`palette::Extended`].      pub fn with_fn( +        name: String,          palette: Palette,          generate: impl FnOnce(Palette) -> palette::Extended,      ) -> Self {          Self { +            name,              palette,              extended: generate(palette),          }      }  } +impl fmt::Display for Custom { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        write!(f, "{}", self.name) +    } +} +  /// The style of an application.  #[derive(Default)]  pub enum Application { @@ -383,6 +407,12 @@ pub enum Container {      Custom(Box<dyn container::StyleSheet<Style = Theme>>),  } +impl From<container::Appearance> for Container { +    fn from(appearance: container::Appearance) -> Self { +        Self::Custom(Box::new(move |_: &_| appearance)) +    } +} +  impl<T: Fn(&Theme) -> container::Appearance + 'static> From<T> for Container {      fn from(f: T) -> Self {          Self::Custom(Box::new(f)) diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs index aaeb799d..76977a29 100644 --- a/style/src/theme/palette.rs +++ b/style/src/theme/palette.rs @@ -82,6 +82,8 @@ pub struct Extended {      pub success: Success,      /// The set of danger colors.      pub danger: Danger, +    /// Whether the palette is dark or not. +    pub is_dark: bool,  }  /// The built-in light variant of an [`Extended`] palette. @@ -113,6 +115,7 @@ impl Extended {                  palette.background,                  palette.text,              ), +            is_dark: is_dark(palette.background),          }      }  } diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs index 3e9bd2a5..d1393b4d 100644 --- a/tiny_skia/src/backend.rs +++ b/tiny_skia/src/backend.rs @@ -1,5 +1,6 @@  use crate::core::{Background, Color, Gradient, Rectangle, Vector};  use crate::graphics::backend; +use crate::graphics::text;  use crate::graphics::Viewport;  use crate::primitive::{self, Primitive}; @@ -444,6 +445,35 @@ impl Backend {                      clip_mask,                  );              } +            Primitive::RawText(text::Raw { +                buffer, +                position, +                color, +                clip_bounds: text_clip_bounds, +            }) => { +                let Some(buffer) = buffer.upgrade() else { +                    return; +                }; + +                let physical_bounds = +                    (*text_clip_bounds + translation) * scale_factor; + +                if !clip_bounds.intersects(&physical_bounds) { +                    return; +                } + +                let clip_mask = (!physical_bounds.is_within(&clip_bounds)) +                    .then_some(clip_mask as &_); + +                self.text_pipeline.draw_raw( +                    &buffer, +                    *position + translation, +                    *color, +                    scale_factor, +                    pixels, +                    clip_mask, +                ); +            }              #[cfg(feature = "image")]              Primitive::Image {                  handle, @@ -513,7 +543,6 @@ impl Backend {                  path,                  paint,                  rule, -                transform,              }) => {                  let bounds = path.bounds(); @@ -536,9 +565,11 @@ impl Backend {                      path,                      paint,                      *rule, -                    transform -                        .post_translate(translation.x, translation.y) -                        .post_scale(scale_factor, scale_factor), +                    tiny_skia::Transform::from_translate( +                        translation.x, +                        translation.y, +                    ) +                    .post_scale(scale_factor, scale_factor),                      clip_mask,                  );              } @@ -546,7 +577,6 @@ impl Backend {                  path,                  paint,                  stroke, -                transform,              }) => {                  let bounds = path.bounds(); @@ -569,9 +599,11 @@ impl Backend {                      path,                      paint,                      stroke, -                    transform -                        .post_translate(translation.x, translation.y) -                        .post_scale(scale_factor, scale_factor), +                    tiny_skia::Transform::from_translate( +                        translation.x, +                        translation.y, +                    ) +                    .post_scale(scale_factor, scale_factor),                      clip_mask,                  );              } diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs index 5f28b737..74a08d38 100644 --- a/tiny_skia/src/geometry.rs +++ b/tiny_skia/src/geometry.rs @@ -1,4 +1,5 @@ -use crate::core::{Point, Rectangle, Size, Vector}; +use crate::core::text::LineHeight; +use crate::core::{Pixels, Point, Rectangle, Size, Vector};  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::stroke::{self, Stroke};  use crate::graphics::geometry::{Path, Style, Text}; @@ -39,17 +40,22 @@ impl Frame {      }      pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { -        let Some(path) = convert_path(path) else { +        let Some(path) = +            convert_path(path).and_then(|path| path.transform(self.transform)) +        else {              return;          }; +          let fill = fill.into(); +        let mut paint = into_paint(fill.style); +        paint.shader.transform(self.transform); +          self.primitives              .push(Primitive::Custom(primitive::Custom::Fill {                  path, -                paint: into_paint(fill.style), +                paint,                  rule: into_fill_rule(fill.rule), -                transform: self.transform,              }));      } @@ -59,76 +65,111 @@ impl Frame {          size: Size,          fill: impl Into<Fill>,      ) { -        let Some(path) = convert_path(&Path::rectangle(top_left, size)) else { +        let Some(path) = convert_path(&Path::rectangle(top_left, size)) +            .and_then(|path| path.transform(self.transform)) +        else {              return;          }; +          let fill = fill.into(); +        let mut paint = tiny_skia::Paint { +            anti_alias: false, +            ..into_paint(fill.style) +        }; +        paint.shader.transform(self.transform); +          self.primitives              .push(Primitive::Custom(primitive::Custom::Fill {                  path, -                paint: tiny_skia::Paint { -                    anti_alias: false, -                    ..into_paint(fill.style) -                }, +                paint,                  rule: into_fill_rule(fill.rule), -                transform: self.transform,              }));      }      pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) { -        let Some(path) = convert_path(path) else { +        let Some(path) = +            convert_path(path).and_then(|path| path.transform(self.transform)) +        else {              return;          };          let stroke = stroke.into();          let skia_stroke = into_stroke(&stroke); +        let mut paint = into_paint(stroke.style); +        paint.shader.transform(self.transform); +          self.primitives              .push(Primitive::Custom(primitive::Custom::Stroke {                  path, -                paint: into_paint(stroke.style), +                paint,                  stroke: skia_stroke, -                transform: self.transform,              }));      }      pub fn fill_text(&mut self, text: impl Into<Text>) {          let text = text.into(); -        let position = if self.transform.is_identity() { -            text.position +        let (scale_x, scale_y) = self.transform.get_scale(); + +        if self.transform.is_scale_translate() +            && scale_x == scale_y +            && scale_x > 0.0 +            && scale_y > 0.0 +        { +            let (position, size, line_height) = if self.transform.is_identity() +            { +                (text.position, text.size, text.line_height) +            } else { +                let mut position = [tiny_skia::Point { +                    x: text.position.x, +                    y: text.position.y, +                }]; + +                self.transform.map_points(&mut position); + +                let size = text.size.0 * scale_y; + +                let line_height = match text.line_height { +                    LineHeight::Absolute(size) => { +                        LineHeight::Absolute(Pixels(size.0 * scale_y)) +                    } +                    LineHeight::Relative(factor) => { +                        LineHeight::Relative(factor) +                    } +                }; + +                ( +                    Point::new(position[0].x, position[0].y), +                    size.into(), +                    line_height, +                ) +            }; + +            let bounds = Rectangle { +                x: position.x, +                y: position.y, +                width: f32::INFINITY, +                height: f32::INFINITY, +            }; + +            // TODO: Honor layering! +            self.primitives.push(Primitive::Text { +                content: text.content, +                bounds, +                color: text.color, +                size, +                line_height, +                font: text.font, +                horizontal_alignment: text.horizontal_alignment, +                vertical_alignment: text.vertical_alignment, +                shaping: text.shaping, +                clip_bounds: Rectangle::with_size(Size::INFINITY), +            });          } else { -            let mut transformed = [tiny_skia::Point { -                x: text.position.x, -                y: text.position.y, -            }]; - -            self.transform.map_points(&mut transformed); - -            Point::new(transformed[0].x, transformed[0].y) -        }; - -        let bounds = Rectangle { -            x: position.x, -            y: position.y, -            width: f32::INFINITY, -            height: f32::INFINITY, -        }; - -        // TODO: Use vectorial text instead of primitive -        self.primitives.push(Primitive::Text { -            content: text.content, -            bounds, -            color: text.color, -            size: text.size, -            line_height: text.line_height, -            font: text.font, -            horizontal_alignment: text.horizontal_alignment, -            vertical_alignment: text.vertical_alignment, -            shaping: text.shaping, -            clip_bounds: Rectangle::with_size(Size::INFINITY), -        }); +            text.draw_with(|path, color| self.fill(&path, color)); +        }      }      pub fn push_transform(&mut self) { diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs index 0ed24969..7718d542 100644 --- a/tiny_skia/src/primitive.rs +++ b/tiny_skia/src/primitive.rs @@ -13,8 +13,6 @@ pub enum Custom {          paint: tiny_skia::Paint<'static>,          /// The fill rule to follow.          rule: tiny_skia::FillRule, -        /// The transform to apply to the path. -        transform: tiny_skia::Transform,      },      /// A path stroked with some paint.      Stroke { @@ -24,8 +22,6 @@ pub enum Custom {          paint: tiny_skia::Paint<'static>,          /// The stroke settings.          stroke: tiny_skia::Stroke, -        /// The transform to apply to the path. -        transform: tiny_skia::Transform,      },  } diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index 70e95d01..9413e311 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -1,7 +1,6 @@  use crate::core::alignment;  use crate::core::text::{LineHeight, Shaping}; -use crate::core::{Color, Font, Pixels, Point, Rectangle}; -use crate::graphics::color; +use crate::core::{Color, Font, Pixels, Point, Rectangle, Size};  use crate::graphics::text::cache::{self, Cache};  use crate::graphics::text::editor;  use crate::graphics::text::font_system; @@ -149,6 +148,33 @@ impl Pipeline {          );      } +    pub fn draw_raw( +        &mut self, +        buffer: &cosmic_text::Buffer, +        position: Point, +        color: Color, +        scale_factor: f32, +        pixels: &mut tiny_skia::PixmapMut<'_>, +        clip_mask: Option<&tiny_skia::Mask>, +    ) { +        let mut font_system = font_system().write().expect("Write font system"); + +        let (width, height) = buffer.size(); + +        draw( +            font_system.raw(), +            &mut self.glyph_cache, +            buffer, +            Rectangle::new(position, Size::new(width, height)), +            color, +            alignment::Horizontal::Left, +            alignment::Vertical::Top, +            scale_factor, +            pixels, +            clip_mask, +        ); +    } +      pub fn trim_cache(&mut self) {          self.cache.get_mut().trim();          self.glyph_cache.trim(); @@ -217,18 +243,7 @@ fn draw(  fn from_color(color: cosmic_text::Color) -> Color {      let [r, g, b, a] = color.as_rgba(); -    if color::GAMMA_CORRECTION { -        // `cosmic_text::Color` is linear RGB in this case, so we -        // need to convert back to sRGB -        Color::from_linear_rgba( -            r as f32 / 255.0, -            g as f32 / 255.0, -            b as f32 / 255.0, -            a as f32 / 255.0, -        ) -    } else { -        Color::from_rgba8(r, g, b, a as f32 / 255.0) -    } +    Color::from_rgba8(r, g, b, a as f32 / 255.0)  }  #[derive(Debug, Clone, Default)] diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs index e0bff67e..4d7f443e 100644 --- a/wgpu/src/geometry.rs +++ b/wgpu/src/geometry.rs @@ -1,5 +1,6 @@  //! Build and draw geometry. -use crate::core::{Point, Rectangle, Size, Vector}; +use crate::core::text::LineHeight; +use crate::core::{Pixels, Point, Rectangle, Size, Vector};  use crate::graphics::color;  use crate::graphics::geometry::fill::{self, Fill};  use crate::graphics::geometry::{ @@ -115,19 +116,31 @@ struct Transforms {  }  #[derive(Debug, Clone, Copy)] -struct Transform { -    raw: lyon::math::Transform, -    is_identity: bool, -} +struct Transform(lyon::math::Transform);  impl Transform { -    /// Transforms the given [Point] by the transformation matrix. -    fn transform_point(&self, point: &mut Point) { +    fn is_identity(&self) -> bool { +        self.0 == lyon::math::Transform::identity() +    } + +    fn is_scale_translation(&self) -> bool { +        self.0.m12.abs() < 2.0 * f32::EPSILON +            && self.0.m21.abs() < 2.0 * f32::EPSILON +    } + +    fn scale(&self) -> (f32, f32) { +        (self.0.m11, self.0.m22) +    } + +    fn transform_point(&self, point: Point) -> Point {          let transformed = self -            .raw +            .0              .transform_point(euclid::Point2D::new(point.x, point.y)); -        point.x = transformed.x; -        point.y = transformed.y; + +        Point { +            x: transformed.x, +            y: transformed.y, +        }      }      fn transform_style(&self, style: Style) -> Style { @@ -142,8 +155,8 @@ impl Transform {      fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {          match &mut gradient {              Gradient::Linear(linear) => { -                self.transform_point(&mut linear.start); -                self.transform_point(&mut linear.end); +                linear.start = self.transform_point(linear.start); +                linear.end = self.transform_point(linear.end);              }          } @@ -163,10 +176,7 @@ impl Frame {              primitives: Vec::new(),              transforms: Transforms {                  previous: Vec::new(), -                current: Transform { -                    raw: lyon::math::Transform::identity(), -                    is_identity: true, -                }, +                current: Transform(lyon::math::Transform::identity()),              },              fill_tessellator: tessellation::FillTessellator::new(),              stroke_tessellator: tessellation::StrokeTessellator::new(), @@ -209,14 +219,14 @@ impl Frame {          let options = tessellation::FillOptions::default()              .with_fill_rule(into_fill_rule(rule)); -        if self.transforms.current.is_identity { +        if self.transforms.current.is_identity() {              self.fill_tessellator.tessellate_path(                  path.raw(),                  &options,                  buffer.as_mut(),              )          } else { -            let path = path.transform(&self.transforms.current.raw); +            let path = path.transform(&self.transforms.current.0);              self.fill_tessellator.tessellate_path(                  path.raw(), @@ -241,13 +251,14 @@ impl Frame {              .buffers              .get_fill(&self.transforms.current.transform_style(style)); -        let top_left = -            self.transforms.current.raw.transform_point( -                lyon::math::Point::new(top_left.x, top_left.y), -            ); +        let top_left = self +            .transforms +            .current +            .0 +            .transform_point(lyon::math::Point::new(top_left.x, top_left.y));          let size = -            self.transforms.current.raw.transform_vector( +            self.transforms.current.0.transform_vector(                  lyon::math::Vector::new(size.width, size.height),              ); @@ -284,14 +295,14 @@ impl Frame {              Cow::Owned(dashed(path, stroke.line_dash))          }; -        if self.transforms.current.is_identity { +        if self.transforms.current.is_identity() {              self.stroke_tessellator.tessellate_path(                  path.raw(),                  &options,                  buffer.as_mut(),              )          } else { -            let path = path.transform(&self.transforms.current.raw); +            let path = path.transform(&self.transforms.current.0);              self.stroke_tessellator.tessellate_path(                  path.raw(), @@ -318,36 +329,57 @@ impl Frame {      pub fn fill_text(&mut self, text: impl Into<Text>) {          let text = text.into(); -        let position = if self.transforms.current.is_identity { -            text.position -        } else { -            let transformed = self.transforms.current.raw.transform_point( -                lyon::math::Point::new(text.position.x, text.position.y), -            ); - -            Point::new(transformed.x, transformed.y) -        }; - -        let bounds = Rectangle { -            x: position.x, -            y: position.y, -            width: f32::INFINITY, -            height: f32::INFINITY, -        }; +        let (scale_x, scale_y) = self.transforms.current.scale(); + +        if self.transforms.current.is_scale_translation() +            && scale_x == scale_y +            && scale_x > 0.0 +            && scale_y > 0.0 +        { +            let (position, size, line_height) = +                if self.transforms.current.is_identity() { +                    (text.position, text.size, text.line_height) +                } else { +                    let position = +                        self.transforms.current.transform_point(text.position); + +                    let size = Pixels(text.size.0 * scale_y); + +                    let line_height = match text.line_height { +                        LineHeight::Absolute(size) => { +                            LineHeight::Absolute(Pixels(size.0 * scale_y)) +                        } +                        LineHeight::Relative(factor) => { +                            LineHeight::Relative(factor) +                        } +                    }; -        // TODO: Use vectorial text instead of primitive -        self.primitives.push(Primitive::Text { -            content: text.content, -            bounds, -            color: text.color, -            size: text.size, -            line_height: text.line_height, -            font: text.font, -            horizontal_alignment: text.horizontal_alignment, -            vertical_alignment: text.vertical_alignment, -            shaping: text.shaping, -            clip_bounds: Rectangle::with_size(Size::INFINITY), -        }); +                    (position, size, line_height) +                }; + +            let bounds = Rectangle { +                x: position.x, +                y: position.y, +                width: f32::INFINITY, +                height: f32::INFINITY, +            }; + +            // TODO: Honor layering! +            self.primitives.push(Primitive::Text { +                content: text.content, +                bounds, +                color: text.color, +                size, +                line_height, +                font: text.font, +                horizontal_alignment: text.horizontal_alignment, +                vertical_alignment: text.vertical_alignment, +                shaping: text.shaping, +                clip_bounds: Rectangle::with_size(Size::INFINITY), +            }); +        } else { +            text.draw_with(|path, color| self.fill(&path, color)); +        }      }      /// Stores the current transform of the [`Frame`] and executes the given @@ -423,26 +455,24 @@ impl Frame {      /// Applies a translation to the current transform of the [`Frame`].      #[inline]      pub fn translate(&mut self, translation: Vector) { -        self.transforms.current.raw = self -            .transforms -            .current -            .raw -            .pre_translate(lyon::math::Vector::new( -                translation.x, -                translation.y, -            )); -        self.transforms.current.is_identity = false; +        self.transforms.current.0 = +            self.transforms +                .current +                .0 +                .pre_translate(lyon::math::Vector::new( +                    translation.x, +                    translation.y, +                ));      }      /// Applies a rotation in radians to the current transform of the [`Frame`].      #[inline]      pub fn rotate(&mut self, angle: f32) { -        self.transforms.current.raw = self +        self.transforms.current.0 = self              .transforms              .current -            .raw +            .0              .pre_rotate(lyon::math::Angle::radians(angle)); -        self.transforms.current.is_identity = false;      }      /// Applies a uniform scaling to the current transform of the [`Frame`]. @@ -458,9 +488,8 @@ impl Frame {      pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {          let scale = scale.into(); -        self.transforms.current.raw = -            self.transforms.current.raw.pre_scale(scale.x, scale.y); -        self.transforms.current.is_identity = false; +        self.transforms.current.0 = +            self.transforms.current.0.pre_scale(scale.x, scale.y);      }      /// Produces the [`Primitive`] representing everything drawn on the [`Frame`]. diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs index 557a7633..4ad12a88 100644 --- a/wgpu/src/layer.rs +++ b/wgpu/src/layer.rs @@ -177,6 +177,21 @@ impl<'a> Layer<'a> {                      clip_bounds: *clip_bounds + translation,                  }));              } +            graphics::Primitive::RawText(graphics::text::Raw { +                buffer, +                position, +                color, +                clip_bounds, +            }) => { +                let layer = &mut layers[current_layer]; + +                layer.text.push(Text::Raw(graphics::text::Raw { +                    buffer: buffer.clone(), +                    position: *position + translation, +                    color: *color, +                    clip_bounds: *clip_bounds + translation, +                })); +            }              Primitive::Quad {                  bounds,                  background, diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs index df2f2875..37ee5247 100644 --- a/wgpu/src/layer/text.rs +++ b/wgpu/src/layer/text.rs @@ -1,6 +1,7 @@  use crate::core::alignment;  use crate::core::text;  use crate::core::{Color, Font, Pixels, Point, Rectangle}; +use crate::graphics;  use crate::graphics::text::editor;  use crate::graphics::text::paragraph; @@ -23,8 +24,10 @@ pub enum Text<'a> {          color: Color,          clip_bounds: Rectangle,      }, -    /// A cached text. +    /// Some cached text.      Cached(Cached<'a>), +    /// Some raw text. +    Raw(graphics::text::Raw),  }  #[derive(Debug, Clone)] diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs index 302e38f6..c8e45458 100644 --- a/wgpu/src/primitive/pipeline.rs +++ b/wgpu/src/primitive/pipeline.rs @@ -82,7 +82,7 @@ impl<Theme> Renderer for crate::Renderer<Theme> {  /// Stores custom, user-provided pipelines.  #[derive(Default, Debug)]  pub struct Storage { -    pipelines: HashMap<TypeId, Box<dyn Any>>, +    pipelines: HashMap<TypeId, Box<dyn Any + Send>>,  }  impl Storage { @@ -92,7 +92,7 @@ impl Storage {      }      /// Inserts the pipeline `T` in to [`Storage`]. -    pub fn store<T: 'static>(&mut self, pipeline: T) { +    pub fn store<T: 'static + Send>(&mut self, pipeline: T) {          let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));      } diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 888b1924..dca09cb8 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -7,6 +7,7 @@ use crate::layer::Text;  use std::borrow::Cow;  use std::cell::RefCell; +use std::sync::Arc;  #[allow(missing_debug_implementations)]  pub struct Pipeline { @@ -76,6 +77,7 @@ impl Pipeline {              Paragraph(Paragraph),              Editor(Editor),              Cache(cache::KeyHash), +            Raw(Arc<glyphon::Buffer>),          }          let allocations: Vec<_> = sections @@ -107,6 +109,7 @@ impl Pipeline {                      Some(Allocation::Cache(key))                  } +                Text::Raw(text) => text.buffer.upgrade().map(Allocation::Raw),              })              .collect(); @@ -185,6 +188,25 @@ impl Pipeline {                              text.clip_bounds,                          )                      } +                    Text::Raw(text) => { +                        let Some(Allocation::Raw(buffer)) = allocation else { +                            return None; +                        }; + +                        let (width, height) = buffer.size(); + +                        ( +                            buffer.as_ref(), +                            Rectangle::new( +                                text.position, +                                Size::new(width, height), +                            ), +                            alignment::Horizontal::Left, +                            alignment::Vertical::Top, +                            text.color, +                            text.clip_bounds, +                        ) +                    }                  };                  let bounds = bounds * scale_factor; diff --git a/widget/src/button.rs b/widget/src/button.rs index 384a3156..0ebb8dcc 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -10,8 +10,8 @@ use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation;  use crate::core::{ -    Background, Clipboard, Color, Element, Layout, Length, Padding, Point, -    Rectangle, Shell, Vector, Widget, +    Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, +    Shell, Size, Vector, Widget,  };  pub use iced_style::button::{Appearance, StyleSheet}; @@ -71,11 +71,14 @@ where  {      /// Creates a new [`Button`] with the given content.      pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self { +        let content = content.into(); +        let size = content.as_widget().size_hint(); +          Button { -            content: content.into(), +            content,              on_press: None, -            width: Length::Shrink, -            height: Length::Shrink, +            width: size.width.fluid(), +            height: size.height.fluid(),              padding: Padding::new(5.0),              style: <Renderer::Theme as StyleSheet>::Style::default(),          } @@ -149,12 +152,11 @@ where          tree.diff_children(std::slice::from_ref(&self.content));      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -431,15 +433,7 @@ pub fn layout(      padding: Padding,      layout_content: impl FnOnce(&layout::Limits) -> layout::Node,  ) -> layout::Node { -    let limits = limits.width(width).height(height); - -    let mut content = layout_content(&limits.pad(padding)); -    let padding = padding.fit(content.size(), limits.max()); -    let size = limits.pad(padding).resolve(content.size()).pad(padding); - -    content.move_to(Point::new(padding.left, padding.top)); - -    layout::Node::with_children(size, vec![content]) +    layout::padded(limits, width, height, padding, layout_content)  }  /// Returns the [`mouse::Interaction`] of a [`Button`]. diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 390f4d92..4e42a671 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -14,8 +14,9 @@ use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree}; -use crate::core::{Clipboard, Element, Shell, Widget}; -use crate::core::{Length, Rectangle, Size, Vector}; +use crate::core::{ +    Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget, +};  use crate::graphics::geometry;  use std::marker::PhantomData; @@ -119,12 +120,11 @@ where          tree::State::new(P::State::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -133,10 +133,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.width, self.height)      }      fn on_event( diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs index 1288365f..a8eb47f7 100644 --- a/widget/src/canvas/event.rs +++ b/widget/src/canvas/event.rs @@ -8,7 +8,7 @@ pub use crate::core::event::Status;  /// A [`Canvas`] event.  ///  /// [`Canvas`]: crate::Canvas -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)]  pub enum Event {      /// A mouse event.      Mouse(mouse::Event), diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index a0d9559b..0353b3ad 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -174,12 +174,11 @@ where          tree::State::new(widget::text::State::<Renderer::Paragraph>::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/column.rs b/widget/src/column.rs index abb522be..d6eea84b 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -7,7 +7,7 @@ use crate::core::renderer;  use crate::core::widget::{Operation, Tree};  use crate::core::{      Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, -    Shell, Widget, +    Shell, Size, Widget,  };  /// A container that distributes its contents vertically. @@ -22,16 +22,12 @@ pub struct Column<'a, Message, Renderer = crate::Renderer> {      children: Vec<Element<'a, Message, Renderer>>,  } -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Column<'a, Message, Renderer> +where +    Renderer: crate::core::Renderer, +{      /// Creates an empty [`Column`].      pub fn new() -> Self { -        Self::with_children(Vec::new()) -    } - -    /// Creates a [`Column`] with the given elements. -    pub fn with_children( -        children: Vec<Element<'a, Message, Renderer>>, -    ) -> Self {          Column {              spacing: 0.0,              padding: Padding::ZERO, @@ -39,10 +35,17 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {              height: Length::Shrink,              max_width: f32::INFINITY,              align_items: Alignment::Start, -            children, +            children: Vec::new(),          }      } +    /// Creates a [`Column`] with the given elements. +    pub fn with_children( +        children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +    ) -> Self { +        children.into_iter().fold(Self::new(), Self::push) +    } +      /// Sets the vertical spacing _between_ elements.      ///      /// Custom margins per element do not exist in iced. You should use this @@ -88,12 +91,26 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {          mut self,          child: impl Into<Element<'a, Message, Renderer>>,      ) -> Self { -        self.children.push(child.into()); +        let child = child.into(); +        let size = child.as_widget().size_hint(); + +        if size.width.is_fill() { +            self.width = Length::Fill; +        } + +        if size.height.is_fill() { +            self.height = Length::Fill; +        } + +        self.children.push(child);          self      }  } -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> +where +    Renderer: crate::core::Renderer, +{      fn default() -> Self {          Self::new()      } @@ -112,12 +129,11 @@ where          tree.diff_children(&self.children);      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -126,15 +142,14 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits -            .max_width(self.max_width) -            .width(self.width) -            .height(self.height); +        let limits = limits.max_width(self.max_width);          layout::flex::resolve(              layout::flex::Axis::Vertical,              renderer,              &limits, +            self.width, +            self.height,              self.padding,              self.spacing,              self.align_items, diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 31ec27fc..73beeac3 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -1,6 +1,7 @@  //! Display a dropdown list of searchable and selectable options.  use crate::core::event::{self, Event};  use crate::core::keyboard; +use crate::core::keyboard::key;  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::overlay; @@ -8,7 +9,9 @@ use crate::core::renderer;  use crate::core::text;  use crate::core::time::Instant;  use crate::core::widget::{self, Widget}; -use crate::core::{Clipboard, Element, Length, Padding, Rectangle, Shell}; +use crate::core::{ +    Clipboard, Element, Length, Padding, Rectangle, Shell, Size, +};  use crate::overlay::menu;  use crate::text::LineHeight;  use crate::{container, scrollable, text_input, TextInput}; @@ -297,12 +300,8 @@ where          + scrollable::StyleSheet          + menu::StyleSheet,  { -    fn width(&self) -> Length { -        Widget::<TextInputEvent, Renderer>::width(&self.text_input) -    } - -    fn height(&self) -> Length { -        Widget::<TextInputEvent, Renderer>::height(&self.text_input) +    fn size(&self) -> Size<Length> { +        Widget::<TextInputEvent, Renderer>::size(&self.text_input)      }      fn layout( @@ -438,14 +437,14 @@ where                  }                  if let Event::Keyboard(keyboard::Event::KeyPressed { -                    key_code, +                    key: keyboard::Key::Named(named_key),                      modifiers,                      ..                  }) = event                  {                      let shift_modifer = modifiers.shift(); -                    match (key_code, shift_modifer) { -                        (keyboard::KeyCode::Enter, _) => { +                    match (named_key, shift_modifer) { +                        (key::Named::Enter, _) => {                              if let Some(index) = &menu.hovered_option {                                  if let Some(option) =                                      state.filtered_options.options.get(*index) @@ -457,8 +456,7 @@ where                              event_status = event::Status::Captured;                          } -                        (keyboard::KeyCode::Up, _) -                        | (keyboard::KeyCode::Tab, true) => { +                        (key::Named::ArrowUp, _) | (key::Named::Tab, true) => {                              if let Some(index) = &mut menu.hovered_option {                                  if *index == 0 {                                      *index = state @@ -494,8 +492,8 @@ where                              event_status = event::Status::Captured;                          } -                        (keyboard::KeyCode::Down, _) -                        | (keyboard::KeyCode::Tab, false) +                        (key::Named::ArrowDown, _) +                        | (key::Named::Tab, false)                              if !modifiers.shift() =>                          {                              if let Some(index) = &mut menu.hovered_option { diff --git a/widget/src/container.rs b/widget/src/container.rs index 5dd7705b..cffb0458 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -46,17 +46,20 @@ where      where          T: Into<Element<'a, Message, Renderer>>,      { +        let content = content.into(); +        let size = content.as_widget().size_hint(); +          Container {              id: None,              padding: Padding::ZERO, -            width: Length::Shrink, -            height: Length::Shrink, +            width: size.width.fluid(), +            height: size.height.fluid(),              max_width: f32::INFINITY,              max_height: f32::INFINITY,              horizontal_alignment: alignment::Horizontal::Left,              vertical_alignment: alignment::Vertical::Top,              style: Default::default(), -            content: content.into(), +            content,          }      } @@ -152,12 +155,11 @@ where          self.content.as_widget().diff(tree);      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -311,25 +313,20 @@ pub fn layout(      vertical_alignment: alignment::Vertical,      layout_content: impl FnOnce(&layout::Limits) -> layout::Node,  ) -> layout::Node { -    let limits = limits -        .loose() -        .max_width(max_width) -        .max_height(max_height) -        .width(width) -        .height(height); - -    let mut content = layout_content(&limits.pad(padding).loose()); -    let padding = padding.fit(content.size(), limits.max()); -    let size = limits.pad(padding).resolve(content.size()); - -    content.move_to(Point::new(padding.left, padding.top)); -    content.align( -        Alignment::from(horizontal_alignment), -        Alignment::from(vertical_alignment), -        size, -    ); - -    layout::Node::with_children(size.pad(padding), vec![content]) +    layout::positioned( +        &limits.max_width(max_width).max_height(max_height), +        width, +        height, +        padding, +        |limits| layout_content(&limits.loose()), +        |content, size| { +            content.align( +                Alignment::from(horizontal_alignment), +                Alignment::from(vertical_alignment), +                size, +            ) +        }, +    )  }  /// Draws the background of a [`Container`] given its [`Appearance`] and its `bounds`. diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 115198fb..498dd76c 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -34,7 +34,7 @@ macro_rules! column {          $crate::Column::new()      );      ($($x:expr),+ $(,)?) => ( -        $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+]) +        $crate::Column::with_children([$($crate::core::Element::from($x)),+])      );  } @@ -47,7 +47,7 @@ macro_rules! row {          $crate::Row::new()      );      ($($x:expr),+ $(,)?) => ( -        $crate::Row::with_children(vec![$($crate::core::Element::from($x)),+]) +        $crate::Row::with_children([$($crate::core::Element::from($x)),+])      );  } @@ -65,9 +65,12 @@ where  }  /// Creates a new [`Column`] with the given children. -pub fn column<Message, Renderer>( -    children: Vec<Element<'_, Message, Renderer>>, -) -> Column<'_, Message, Renderer> { +pub fn column<'a, Message, Renderer>( +    children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +) -> Column<'a, Message, Renderer> +where +    Renderer: core::Renderer, +{      Column::with_children(children)  } @@ -77,6 +80,7 @@ pub fn keyed_column<'a, Key, Message, Renderer>(  ) -> keyed::Column<'a, Key, Message, Renderer>  where      Key: Copy + PartialEq, +    Renderer: core::Renderer,  {      keyed::Column::with_children(children)  } @@ -84,9 +88,12 @@ where  /// Creates a new [`Row`] with the given children.  ///  /// [`Row`]: crate::Row -pub fn row<Message, Renderer>( -    children: Vec<Element<'_, Message, Renderer>>, -) -> Row<'_, Message, Renderer> { +pub fn row<'a, Message, Renderer>( +    children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +) -> Row<'a, Message, Renderer> +where +    Renderer: core::Renderer, +{      Row::with_children(children)  } @@ -264,7 +271,7 @@ pub fn pick_list<'a, Message, Renderer, T>(      on_selected: impl Fn(T) -> Message + 'a,  ) -> PickList<'a, T, Message, Renderer>  where -    T: ToString + Eq + 'static, +    T: ToString + PartialEq + 'static,      [T]: ToOwned<Owned = Vec<T>>,      Renderer: core::text::Renderer,      Renderer::Theme: pick_list::StyleSheet diff --git a/widget/src/image.rs b/widget/src/image.rs index 67699102..e906ac13 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -99,7 +99,7 @@ where      };      // The size to be available to the widget prior to `Shrink`ing -    let raw_size = limits.width(width).height(height).resolve(image_size); +    let raw_size = limits.resolve(width, height, image_size);      // The uncropped size of the image when fit to the bounds above      let full_size = content_fit.fit(image_size, raw_size); @@ -164,12 +164,11 @@ where      Renderer: image::Renderer<Handle = Handle>,      Handle: Clone + Hash,  { -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 68015ba8..98080577 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -97,12 +97,11 @@ where          tree::State::new(State::new())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -113,10 +112,11 @@ where      ) -> layout::Node {          let Size { width, height } = renderer.dimensions(&self.handle); -        let mut size = limits -            .width(self.width) -            .height(self.height) -            .resolve(Size::new(width as f32, height as f32)); +        let mut size = limits.resolve( +            self.width, +            self.height, +            Size::new(width as f32, height as f32), +        );          let expansion_size = if height > width {              self.width diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 0ef82407..7f05a81e 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -8,7 +8,7 @@ use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation;  use crate::core::{      Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, -    Shell, Widget, +    Shell, Size, Widget,  };  /// A container that distributes its contents vertically. @@ -30,26 +30,10 @@ where  impl<'a, Key, Message, Renderer> Column<'a, Key, Message, Renderer>  where      Key: Copy + PartialEq, +    Renderer: crate::core::Renderer,  {      /// Creates an empty [`Column`].      pub fn new() -> Self { -        Self::with_children(Vec::new()) -    } - -    /// Creates a [`Column`] with the given elements. -    pub fn with_children( -        children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>, -    ) -> Self { -        let (keys, children) = children.into_iter().fold( -            (Vec::new(), Vec::new()), -            |(mut keys, mut children), (key, child)| { -                keys.push(key); -                children.push(child); - -                (keys, children) -            }, -        ); -          Column {              spacing: 0.0,              padding: Padding::ZERO, @@ -57,11 +41,20 @@ where              height: Length::Shrink,              max_width: f32::INFINITY,              align_items: Alignment::Start, -            keys, -            children, +            keys: Vec::new(), +            children: Vec::new(),          }      } +    /// Creates a [`Column`] with the given elements. +    pub fn with_children( +        children: impl IntoIterator<Item = (Key, Element<'a, Message, Renderer>)>, +    ) -> Self { +        children +            .into_iter() +            .fold(Self::new(), |column, (key, child)| column.push(key, child)) +    } +      /// Sets the vertical spacing _between_ elements.      ///      /// Custom margins per element do not exist in iced. You should use this @@ -108,8 +101,19 @@ where          key: Key,          child: impl Into<Element<'a, Message, Renderer>>,      ) -> Self { +        let child = child.into(); +        let size = child.as_widget().size_hint(); + +        if size.width.is_fill() { +            self.width = Length::Fill; +        } + +        if size.height.is_fill() { +            self.height = Length::Fill; +        } +          self.keys.push(key); -        self.children.push(child.into()); +        self.children.push(child);          self      }  } @@ -117,6 +121,7 @@ where  impl<'a, Key, Message, Renderer> Default for Column<'a, Key, Message, Renderer>  where      Key: Copy + PartialEq, +    Renderer: crate::core::Renderer,  {      fn default() -> Self {          Self::new() @@ -173,12 +178,11 @@ where          }      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -196,6 +200,8 @@ where              layout::flex::Axis::Vertical,              renderer,              &limits, +            self.width, +            self.height,              self.padding,              self.spacing,              self.align_items, diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 167a055d..e9edbb4c 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -142,12 +142,15 @@ where          }      } -    fn width(&self) -> Length { -        self.with_element(|element| element.as_widget().width()) +    fn size(&self) -> Size<Length> { +        self.with_element(|element| element.as_widget().size())      } -    fn height(&self) -> Length { -        self.with_element(|element| element.as_widget().height()) +    fn size_hint(&self) -> Size<Length> { +        Size { +            width: Length::Shrink, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index ad0c3823..3684e0c9 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -244,12 +244,15 @@ where          self.rebuild_element_if_necessary();      } -    fn width(&self) -> Length { -        self.with_element(|element| element.as_widget().width()) +    fn size(&self) -> Size<Length> { +        self.with_element(|element| element.as_widget().size())      } -    fn height(&self) -> Length { -        self.with_element(|element| element.as_widget().height()) +    fn size_hint(&self) -> Size<Length> { +        Size { +            width: Length::Shrink, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/lazy/helpers.rs b/widget/src/lazy/helpers.rs index 8ca9cb86..5dc60d52 100644 --- a/widget/src/lazy/helpers.rs +++ b/widget/src/lazy/helpers.rs @@ -6,6 +6,7 @@ use std::hash::Hash;  /// Creates a new [`Lazy`] widget with the given data `Dependency` and a  /// closure that can turn this data into a widget tree. +#[cfg(feature = "lazy")]  pub fn lazy<'a, Message, Renderer, Dependency, View>(      dependency: Dependency,      view: impl Fn(&Dependency) -> View + 'a, @@ -19,6 +20,7 @@ where  /// Turns an implementor of [`Component`] into an [`Element`] that can be  /// embedded in any application. +#[cfg(feature = "lazy")]  pub fn component<'a, C, Message, Renderer>(      component: C,  ) -> Element<'a, Message, Renderer> @@ -37,6 +39,7 @@ where  /// The `view` closure will be provided with the current [`Size`] of  /// the [`Responsive`] widget and, therefore, can be used to build the  /// contents of the widget in a responsive way. +#[cfg(feature = "lazy")]  pub fn responsive<'a, Message, Renderer>(      f: impl Fn(Size) -> Element<'a, Message, Renderer> + 'a,  ) -> Responsive<'a, Message, Renderer> diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 86d37b6c..1df0866f 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -135,12 +135,11 @@ where          })      } -    fn width(&self) -> Length { -        Length::Fill -    } - -    fn height(&self) -> Length { -        Length::Fill +    fn size(&self) -> Size<Length> { +        Size { +            width: Length::Fill, +            height: Length::Fill, +        }      }      fn layout( diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 3a5b01a3..87cac3a7 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -8,7 +8,7 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::{tree, Operation, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Length, Rectangle, Shell, Widget, +    Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Widget,  };  /// Emit messages on mouse events. @@ -110,12 +110,8 @@ where          tree.diff_children(std::slice::from_ref(&self.content));      } -    fn width(&self) -> Length { -        self.content.as_widget().width() -    } - -    fn height(&self) -> Length { -        self.content.as_widget().height() +    fn size(&self) -> Size<Length> { +        self.content.as_widget().size()      }      fn layout( diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index e45b44ae..f83eebea 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -254,15 +254,14 @@ where          )          .width(self.width); -        let mut node = self.container.layout(self.state, renderer, &limits); +        let node = self.container.layout(self.state, renderer, &limits); +        let size = node.size();          node.move_to(if space_below > space_above {              position + Vector::new(0.0, self.target_height)          } else { -            position - Vector::new(0.0, node.size().height) -        }); - -        node +            position - Vector::new(0.0, size.height) +        })      }      fn on_event( @@ -343,12 +342,11 @@ where      Renderer: text::Renderer,      Renderer::Theme: StyleSheet,  { -    fn width(&self) -> Length { -        Length::Fill -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: Length::Fill, +            height: Length::Shrink, +        }      }      fn layout( @@ -359,7 +357,6 @@ where      ) -> layout::Node {          use std::f32; -        let limits = limits.width(Length::Fill).height(Length::Shrink);          let text_size =              self.text_size.unwrap_or_else(|| renderer.default_size()); @@ -372,7 +369,7 @@ where                      * self.options.len() as f32,              ); -            limits.resolve(intrinsic) +            limits.resolve(Length::Fill, Length::Shrink, intrinsic)          };          layout::Node::new(size) diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 2d25a543..cf1f0455 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -265,12 +265,11 @@ where          }      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -490,8 +489,7 @@ pub fn layout<Renderer, T>(          &layout::Limits,      ) -> layout::Node,  ) -> layout::Node { -    let limits = limits.width(width).height(height); -    let size = limits.resolve(Size::ZERO); +    let size = limits.resolve(width, height, Size::ZERO);      let regions = node.pane_regions(spacing, size);      let children = contents @@ -500,16 +498,14 @@ pub fn layout<Renderer, T>(              let region = regions.get(&pane)?;              let size = Size::new(region.width, region.height); -            let mut node = layout_content( +            let node = layout_content(                  content,                  tree,                  renderer,                  &layout::Limits::new(size, size),              ); -            node.move_to(Point::new(region.x, region.y)); - -            Some(node) +            Some(node.move_to(Point::new(region.x, region.y)))          })          .collect(); @@ -531,6 +527,8 @@ pub fn update<'a, Message, T: Draggable>(      on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,      on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,  ) -> event::Status { +    const DRAG_DEADBAND_DISTANCE: f32 = 10.0; +      let mut event_status = event::Status::Ignored;      match event { @@ -572,7 +570,6 @@ pub fn update<'a, Message, T: Draggable>(                                  shell,                                  contents,                                  on_click, -                                on_drag,                              );                          }                      } @@ -584,7 +581,6 @@ pub fn update<'a, Message, T: Draggable>(                              shell,                              contents,                              on_click, -                            on_drag,                          );                      }                  } @@ -637,7 +633,49 @@ pub fn update<'a, Message, T: Draggable>(          }          Event::Mouse(mouse::Event::CursorMoved { .. })          | Event::Touch(touch::Event::FingerMoved { .. }) => { -            if let Some((_, on_resize)) = on_resize { +            if let Some((_, origin)) = action.clicked_pane() { +                if let Some(on_drag) = &on_drag { +                    let bounds = layout.bounds(); + +                    if let Some(cursor_position) = cursor.position_over(bounds) +                    { +                        let mut clicked_region = contents +                            .zip(layout.children()) +                            .filter(|(_, layout)| { +                                layout.bounds().contains(cursor_position) +                            }); + +                        if let Some(((pane, content), layout)) = +                            clicked_region.next() +                        { +                            if content +                                .can_be_dragged_at(layout, cursor_position) +                            { +                                let pane_position = layout.position(); + +                                let new_origin = cursor_position +                                    - Vector::new( +                                        pane_position.x, +                                        pane_position.y, +                                    ); + +                                if new_origin.distance(origin) +                                    > DRAG_DEADBAND_DISTANCE +                                { +                                    *action = state::Action::Dragging { +                                        pane, +                                        origin, +                                    }; + +                                    shell.publish(on_drag(DragEvent::Picked { +                                        pane, +                                    })); +                                } +                            } +                        } +                    } +                } +            } else if let Some((_, on_resize)) = on_resize {                  if let Some((split, _)) = action.picked_split() {                      let bounds = layout.bounds(); @@ -712,7 +750,6 @@ fn click_pane<'a, Message, T>(      shell: &mut Shell<'_, Message>,      contents: impl Iterator<Item = (Pane, T)>,      on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, -    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,  ) where      T: Draggable,  { @@ -720,23 +757,15 @@ fn click_pane<'a, Message, T>(          .zip(layout.children())          .filter(|(_, layout)| layout.bounds().contains(cursor_position)); -    if let Some(((pane, content), layout)) = clicked_region.next() { +    if let Some(((pane, _), layout)) = clicked_region.next() {          if let Some(on_click) = &on_click {              shell.publish(on_click(pane));          } -        if let Some(on_drag) = &on_drag { -            if content.can_be_dragged_at(layout, cursor_position) { -                let pane_position = layout.position(); - -                let origin = cursor_position -                    - Vector::new(pane_position.x, pane_position.y); - -                *action = state::Action::Dragging { pane, origin }; - -                shell.publish(on_drag(DragEvent::Picked { pane })); -            } -        } +        let pane_position = layout.position(); +        let origin = +            cursor_position - Vector::new(pane_position.x, pane_position.y); +        *action = state::Action::Clicking { pane, origin };      }  } @@ -749,7 +778,7 @@ pub fn mouse_interaction(      spacing: f32,      resize_leeway: Option<f32>,  ) -> Option<mouse::Interaction> { -    if action.picked_pane().is_some() { +    if action.clicked_pane().is_some() || action.picked_pane().is_some() {          return Some(mouse::Interaction::Grabbing);      } diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 826ea663..ee00f186 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -165,7 +165,7 @@ where              let title_bar_size = title_bar_layout.size(); -            let mut body_layout = self.body.as_widget().layout( +            let body_layout = self.body.as_widget().layout(                  &mut tree.children[0],                  renderer,                  &layout::Limits::new( @@ -177,11 +177,12 @@ where                  ),              ); -            body_layout.move_to(Point::new(0.0, title_bar_size.height)); -              layout::Node::with_children(                  max_size, -                vec![title_bar_layout, body_layout], +                vec![ +                    title_bar_layout, +                    body_layout.move_to(Point::new(0.0, title_bar_size.height)), +                ],              )          } else {              self.body.as_widget().layout( diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 481cd770..5d1fe254 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -403,6 +403,15 @@ pub enum Action {      ///      /// [`PaneGrid`]: super::PaneGrid      Idle, +    /// A [`Pane`] in the [`PaneGrid`] is being clicked. +    /// +    /// [`PaneGrid`]: super::PaneGrid +    Clicking { +        /// The [`Pane`] being clicked. +        pane: Pane, +        /// The starting [`Point`] of the click interaction. +        origin: Point, +    },      /// A [`Pane`] in the [`PaneGrid`] is being dragged.      ///      /// [`PaneGrid`]: super::PaneGrid @@ -432,6 +441,14 @@ impl Action {          }      } +    /// Returns the current [`Pane`] that is being clicked, if any. +    pub fn clicked_pane(&self) -> Option<(Pane, Point)> { +        match *self { +            Action::Clicking { pane, origin, .. } => Some((pane, origin)), +            _ => None, +        } +    } +      /// Returns the current [`Split`] that is being dragged, if any.      pub fn picked_split(&self) -> Option<(Split, Axis)> {          match *self { diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index f4dbb6b1..eb21b743 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -217,7 +217,7 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.pad(self.padding); +        let limits = limits.shrink(self.padding);          let max_size = limits.max();          let title_layout = self.content.as_widget().layout( @@ -228,8 +228,8 @@ where          let title_size = title_layout.size(); -        let mut node = if let Some(controls) = &self.controls { -            let mut controls_layout = controls.as_widget().layout( +        let node = if let Some(controls) = &self.controls { +            let controls_layout = controls.as_widget().layout(                  &mut tree.children[1],                  renderer,                  &layout::Limits::new(Size::ZERO, max_size), @@ -240,11 +240,13 @@ where              let height = title_size.height.max(controls_size.height); -            controls_layout.move_to(Point::new(space_before_controls, 0.0)); -              layout::Node::with_children(                  Size::new(max_size.width, height), -                vec![title_layout, controls_layout], +                vec![ +                    title_layout, +                    controls_layout +                        .move_to(Point::new(space_before_controls, 0.0)), +                ],              )          } else {              layout::Node::with_children( @@ -253,9 +255,7 @@ where              )          }; -        node.move_to(Point::new(self.padding.left, self.padding.top)); - -        layout::Node::with_children(node.size().pad(self.padding), vec![node]) +        layout::Node::container(node, self.padding)      }      pub(crate) fn operate( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 022ca8d9..2e3aab6f 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -45,7 +45,7 @@ where  impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>  where -    T: ToString + Eq, +    T: ToString + PartialEq,      [T]: ToOwned<Owned = Vec<T>>,      Renderer: text::Renderer,      Renderer::Theme: StyleSheet @@ -145,7 +145,7 @@ where  impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>      for PickList<'a, T, Message, Renderer>  where -    T: Clone + ToString + Eq + 'static, +    T: Clone + ToString + PartialEq + 'static,      [T]: ToOwned<Owned = Vec<T>>,      Message: 'a,      Renderer: text::Renderer + 'a, @@ -164,12 +164,11 @@ where          tree::State::new(State::<Renderer::Paragraph>::new())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( @@ -282,7 +281,7 @@ where  impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    T: Clone + ToString + Eq + 'static, +    T: Clone + ToString + PartialEq + 'static,      [T]: ToOwned<Owned = Vec<T>>,      Message: 'a,      Renderer: text::Renderer + 'a, @@ -393,7 +392,6 @@ where  {      use std::f32; -    let limits = limits.width(width).height(Length::Shrink).pad(padding);      let font = font.unwrap_or_else(|| renderer.default_font());      let text_size = text_size.unwrap_or_else(|| renderer.default_size()); @@ -451,7 +449,11 @@ where              f32::from(text_line_height.to_absolute(text_size)),          ); -        limits.resolve(intrinsic).pad(padding) +        limits +            .width(width) +            .shrink(padding) +            .resolve(width, Length::Shrink, intrinsic) +            .expand(padding)      };      layout::Node::new(size) diff --git a/widget/src/progress_bar.rs b/widget/src/progress_bar.rs index 07de72d5..15f1277b 100644 --- a/widget/src/progress_bar.rs +++ b/widget/src/progress_bar.rs @@ -85,12 +85,11 @@ where      Renderer: crate::core::Renderer,      Renderer::Theme: StyleSheet,  { -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)) +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)), +        }      }      fn layout( @@ -99,13 +98,11 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits -            .width(self.width) -            .height(self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT))); - -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic( +            limits, +            self.width, +            self.height.unwrap_or(Length::Fixed(Self::DEFAULT_HEIGHT)), +        )      }      fn draw( diff --git a/widget/src/qr_code.rs b/widget/src/qr_code.rs index 1dc4da7f..a229eb59 100644 --- a/widget/src/qr_code.rs +++ b/widget/src/qr_code.rs @@ -50,12 +50,11 @@ impl<'a> QRCode<'a> {  }  impl<'a, Message, Theme> Widget<Message, Renderer<Theme>> for QRCode<'a> { -    fn width(&self) -> Length { -        Length::Shrink -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: Length::Shrink, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/radio.rs b/widget/src/radio.rs index ae2365dd..f91b20b1 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -201,12 +201,11 @@ where          tree::State::new(widget::text::State::<Renderer::Paragraph>::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/row.rs b/widget/src/row.rs index d52b8c43..90fd2926 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -7,7 +7,7 @@ use crate::core::renderer;  use crate::core::widget::{Operation, Tree};  use crate::core::{      Alignment, Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, -    Widget, +    Size, Widget,  };  /// A container that distributes its contents horizontally. @@ -21,26 +21,29 @@ pub struct Row<'a, Message, Renderer = crate::Renderer> {      children: Vec<Element<'a, Message, Renderer>>,  } -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Row<'a, Message, Renderer> +where +    Renderer: crate::core::Renderer, +{      /// Creates an empty [`Row`].      pub fn new() -> Self { -        Self::with_children(Vec::new()) -    } - -    /// Creates a [`Row`] with the given elements. -    pub fn with_children( -        children: Vec<Element<'a, Message, Renderer>>, -    ) -> Self {          Row {              spacing: 0.0,              padding: Padding::ZERO,              width: Length::Shrink,              height: Length::Shrink,              align_items: Alignment::Start, -            children, +            children: Vec::new(),          }      } +    /// Creates a [`Row`] with the given elements. +    pub fn with_children( +        children: impl IntoIterator<Item = Element<'a, Message, Renderer>>, +    ) -> Self { +        children.into_iter().fold(Self::new(), Self::push) +    } +      /// Sets the horizontal spacing _between_ elements.      ///      /// Custom margins per element do not exist in iced. You should use this @@ -80,12 +83,26 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {          mut self,          child: impl Into<Element<'a, Message, Renderer>>,      ) -> Self { -        self.children.push(child.into()); +        let child = child.into(); +        let size = child.as_widget().size_hint(); + +        if size.width.is_fill() { +            self.width = Length::Fill; +        } + +        if size.height.is_fill() { +            self.height = Length::Fill; +        } + +        self.children.push(child);          self      }  } -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> +where +    Renderer: crate::core::Renderer, +{      fn default() -> Self {          Self::new()      } @@ -104,12 +121,11 @@ where          tree.diff_children(&self.children);      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -118,12 +134,12 @@ where          renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -          layout::flex::resolve(              layout::flex::Axis::Horizontal,              renderer, -            &limits, +            limits, +            self.width, +            self.height,              self.padding,              self.spacing,              self.align_items, diff --git a/widget/src/rule.rs b/widget/src/rule.rs index b5c5fa55..cded9cb1 100644 --- a/widget/src/rule.rs +++ b/widget/src/rule.rs @@ -62,12 +62,11 @@ where      Renderer: crate::core::Renderer,      Renderer::Theme: StyleSheet,  { -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -76,9 +75,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        layout::Node::new(limits.resolve(Size::ZERO)) +        layout::atomic(limits, self.width, self.height)      }      fn draw( diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 49aed2f0..70db490a 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -220,12 +220,11 @@ where          tree.diff_children(std::slice::from_ref(&self.content));      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -470,28 +469,25 @@ pub fn layout<Renderer>(      direction: &Direction,      layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,  ) -> layout::Node { -    let limits = limits.width(width).height(height); - -    let child_limits = layout::Limits::new( -        Size::new(limits.min().width, limits.min().height), -        Size::new( -            if direction.horizontal().is_some() { -                f32::INFINITY -            } else { -                limits.max().width -            }, -            if direction.vertical().is_some() { -                f32::MAX -            } else { -                limits.max().height -            }, -        ), -    ); - -    let content = layout_content(renderer, &child_limits); -    let size = limits.resolve(content.size()); +    layout::contained(limits, width, height, |limits| { +        let child_limits = layout::Limits::new( +            Size::new(limits.min().width, limits.min().height), +            Size::new( +                if direction.horizontal().is_some() { +                    f32::INFINITY +                } else { +                    limits.max().width +                }, +                if direction.vertical().is_some() { +                    f32::MAX +                } else { +                    limits.max().height +                }, +            ), +        ); -    layout::Node::with_children(size, vec![content]) +        layout_content(renderer, &child_limits) +    })  }  /// Processes an [`Event`] and updates the [`State`] of a [`Scrollable`] diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 8e334693..16b68c55 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -70,12 +70,11 @@ where          tree::State::new(P::State::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -84,10 +83,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.width, self.height)      }      fn on_event( diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index 1cc484fb..005c8725 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -9,7 +9,7 @@ pub use crate::core::event::Status;  /// A [`Shader`] event.  ///  /// [`Shader`]: crate::Shader -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)]  pub enum Event {      /// A mouse event.      Mouse(mouse::Event), diff --git a/widget/src/slider.rs b/widget/src/slider.rs index ac0982c8..1bc94661 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -159,12 +159,11 @@ where          tree::State::new(State::new())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( @@ -173,10 +172,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.width, self.height)      }      fn on_event( diff --git a/widget/src/space.rs b/widget/src/space.rs index e5a8f169..eef990d1 100644 --- a/widget/src/space.rs +++ b/widget/src/space.rs @@ -45,12 +45,11 @@ impl<Message, Renderer> Widget<Message, Renderer> for Space  where      Renderer: core::Renderer,  { -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -59,9 +58,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); - -        layout::Node::new(limits.resolve(Size::ZERO)) +        layout::atomic(limits, self.width, self.height)      }      fn draw( diff --git a/widget/src/svg.rs b/widget/src/svg.rs index 05c9265b..2357cf65 100644 --- a/widget/src/svg.rs +++ b/widget/src/svg.rs @@ -96,12 +96,11 @@ where      Renderer: svg::Renderer,      Renderer::Theme: iced_style::svg::StyleSheet,  { -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -115,10 +114,7 @@ where          let image_size = Size::new(width as f32, height as f32);          // The size to be available to the widget prior to `Shrink`ing -        let raw_size = limits -            .width(self.width) -            .height(self.height) -            .resolve(image_size); +        let raw_size = limits.resolve(self.width, self.height, image_size);          // The uncropped size of the image when fit to the bounds above          let full_size = self.content_fit.fit(image_size, raw_size); diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a2a186f0..09a0cac0 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -1,6 +1,7 @@  //! Display a multi-line text input for text editing.  use crate::core::event::{self, Event};  use crate::core::keyboard; +use crate::core::keyboard::key;  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::renderer; @@ -9,7 +10,7 @@ use crate::core::text::highlighter::{self, Highlighter};  use crate::core::text::{self, LineHeight};  use crate::core::widget::{self, Widget};  use crate::core::{ -    Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, +    Clipboard, Color, Element, Length, Padding, Pixels, Rectangle, Shell, Size,      Vector,  }; @@ -316,12 +317,11 @@ where          })      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: self.height, +        }      }      fn layout( @@ -350,7 +350,7 @@ where          }          internal.editor.update( -            limits.pad(self.padding).max(), +            limits.shrink(self.padding).max(),              self.font.unwrap_or_else(|| renderer.default_font()),              self.text_size.unwrap_or_else(|| renderer.default_size()),              self.line_height, @@ -647,43 +647,61 @@ impl Update {              },              Event::Keyboard(event) => match event {                  keyboard::Event::KeyPressed { -                    key_code, +                    key,                      modifiers, +                    text, +                    ..                  } if state.is_focused => { -                    if let Some(motion) = motion(key_code) { -                        let motion = -                            if platform::is_jump_modifier_pressed(modifiers) { +                    if let keyboard::Key::Named(named_key) = key.as_ref() { +                        if let Some(motion) = motion(named_key) { +                            let motion = if platform::is_jump_modifier_pressed( +                                modifiers, +                            ) {                                  motion.widen()                              } else {                                  motion                              }; -                        return action(if modifiers.shift() { -                            Action::Select(motion) -                        } else { -                            Action::Move(motion) -                        }); +                            return action(if modifiers.shift() { +                                Action::Select(motion) +                            } else { +                                Action::Move(motion) +                            }); +                        }                      } -                    match key_code { -                        keyboard::KeyCode::Enter => edit(Edit::Enter), -                        keyboard::KeyCode::Backspace => edit(Edit::Backspace), -                        keyboard::KeyCode::Delete => edit(Edit::Delete), -                        keyboard::KeyCode::Escape => Some(Self::Unfocus), -                        keyboard::KeyCode::C if modifiers.command() => { +                    match key.as_ref() { +                        keyboard::Key::Named(key::Named::Enter) => { +                            edit(Edit::Enter) +                        } +                        keyboard::Key::Named(key::Named::Backspace) => { +                            edit(Edit::Backspace) +                        } +                        keyboard::Key::Named(key::Named::Delete) => { +                            edit(Edit::Delete) +                        } +                        keyboard::Key::Named(key::Named::Escape) => { +                            Some(Self::Unfocus) +                        } +                        keyboard::Key::Character("c") +                            if modifiers.command() => +                        {                              Some(Self::Copy)                          } -                        keyboard::KeyCode::V +                        keyboard::Key::Character("v")                              if modifiers.command() && !modifiers.alt() =>                          {                              Some(Self::Paste)                          } -                        _ => None, +                        _ => { +                            let text = text?; + +                            edit(Edit::Insert( +                                text.chars().next().unwrap_or_default(), +                            )) +                        }                      }                  } -                keyboard::Event::CharacterReceived(c) if state.is_focused => { -                    edit(Edit::Insert(c)) -                }                  _ => None,              },              _ => None, @@ -691,16 +709,16 @@ impl Update {      }  } -fn motion(key_code: keyboard::KeyCode) -> Option<Motion> { -    match key_code { -        keyboard::KeyCode::Left => Some(Motion::Left), -        keyboard::KeyCode::Right => Some(Motion::Right), -        keyboard::KeyCode::Up => Some(Motion::Up), -        keyboard::KeyCode::Down => Some(Motion::Down), -        keyboard::KeyCode::Home => Some(Motion::Home), -        keyboard::KeyCode::End => Some(Motion::End), -        keyboard::KeyCode::PageUp => Some(Motion::PageUp), -        keyboard::KeyCode::PageDown => Some(Motion::PageDown), +fn motion(key: key::Named) -> Option<Motion> { +    match key { +        key::Named::ArrowLeft => Some(Motion::Left), +        key::Named::ArrowRight => Some(Motion::Right), +        key::Named::ArrowUp => Some(Motion::Up), +        key::Named::ArrowDown => Some(Motion::Down), +        key::Named::Home => Some(Motion::Home), +        key::Named::End => Some(Motion::End), +        key::Named::PageUp => Some(Motion::PageUp), +        key::Named::PageDown => Some(Motion::PageDown),          _ => None,      }  } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 65d3e1eb..c3dce8be 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -14,6 +14,7 @@ use editor::Editor;  use crate::core::alignment;  use crate::core::event::{self, Event};  use crate::core::keyboard; +use crate::core::keyboard::key;  use crate::core::layout;  use crate::core::mouse::{self, click};  use crate::core::renderer; @@ -283,12 +284,11 @@ where          }      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( @@ -506,14 +506,11 @@ where  {      let font = font.unwrap_or_else(|| renderer.default_font());      let text_size = size.unwrap_or_else(|| renderer.default_size()); -      let padding = padding.fit(Size::ZERO, limits.max()); -    let limits = limits -        .width(width) -        .pad(padding) -        .height(line_height.to_absolute(text_size)); +    let height = line_height.to_absolute(text_size); -    let text_bounds = limits.resolve(Size::ZERO); +    let limits = limits.width(width).shrink(padding); +    let text_bounds = limits.resolve(width, height, Size::ZERO);      let placeholder_text = Text {          font, @@ -552,41 +549,41 @@ where          let icon_width = state.icon.min_width(); -        let mut text_node = layout::Node::new( -            text_bounds - Size::new(icon_width + icon.spacing, 0.0), -        ); - -        let mut icon_node = -            layout::Node::new(Size::new(icon_width, text_bounds.height)); - -        match icon.side { -            Side::Left => { -                text_node.move_to(Point::new( +        let (text_position, icon_position) = match icon.side { +            Side::Left => ( +                Point::new(                      padding.left + icon_width + icon.spacing,                      padding.top, -                )); - -                icon_node.move_to(Point::new(padding.left, padding.top)); -            } -            Side::Right => { -                text_node.move_to(Point::new(padding.left, padding.top)); - -                icon_node.move_to(Point::new( +                ), +                Point::new(padding.left, padding.top), +            ), +            Side::Right => ( +                Point::new(padding.left, padding.top), +                Point::new(                      padding.left + text_bounds.width - icon_width,                      padding.top, -                )); -            } +                ), +            ),          }; +        let text_node = layout::Node::new( +            text_bounds - Size::new(icon_width + icon.spacing, 0.0), +        ) +        .move_to(text_position); + +        let icon_node = +            layout::Node::new(Size::new(icon_width, text_bounds.height)) +                .move_to(icon_position); +          layout::Node::with_children( -            text_bounds.pad(padding), +            text_bounds.expand(padding),              vec![text_node, icon_node],          )      } else { -        let mut text = layout::Node::new(text_bounds); -        text.move_to(Point::new(padding.left, padding.top)); +        let text = layout::Node::new(text_bounds) +            .move_to(Point::new(padding.left, padding.top)); -        layout::Node::with_children(text_bounds.pad(padding), vec![text]) +        layout::Node::with_children(text_bounds.expand(padding), vec![text])      }  } @@ -752,34 +749,7 @@ where                  return event::Status::Captured;              }          } -        Event::Keyboard(keyboard::Event::CharacterReceived(c)) => { -            let state = state(); - -            if let Some(focus) = &mut state.is_focused { -                let Some(on_input) = on_input else { -                    return event::Status::Ignored; -                }; - -                if state.is_pasting.is_none() -                    && !state.keyboard_modifiers.command() -                    && !c.is_control() -                { -                    let mut editor = Editor::new(value, &mut state.cursor); - -                    editor.insert(c); - -                    let message = (on_input)(editor.contents()); -                    shell.publish(message); - -                    focus.updated_at = Instant::now(); - -                    update_cache(state, value); - -                    return event::Status::Captured; -                } -            } -        } -        Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => { +        Event::Keyboard(keyboard::Event::KeyPressed { key, text, .. }) => {              let state = state();              if let Some(focus) = &mut state.is_focused { @@ -790,14 +760,13 @@ where                  let modifiers = state.keyboard_modifiers;                  focus.updated_at = Instant::now(); -                match key_code { -                    keyboard::KeyCode::Enter -                    | keyboard::KeyCode::NumpadEnter => { +                match key.as_ref() { +                    keyboard::Key::Named(key::Named::Enter) => {                          if let Some(on_submit) = on_submit.clone() {                              shell.publish(on_submit);                          }                      } -                    keyboard::KeyCode::Backspace => { +                    keyboard::Key::Named(key::Named::Backspace) => {                          if platform::is_jump_modifier_pressed(modifiers)                              && state.cursor.selection(value).is_none()                          { @@ -817,7 +786,7 @@ where                          update_cache(state, value);                      } -                    keyboard::KeyCode::Delete => { +                    keyboard::Key::Named(key::Named::Delete) => {                          if platform::is_jump_modifier_pressed(modifiers)                              && state.cursor.selection(value).is_none()                          { @@ -839,7 +808,7 @@ where                          update_cache(state, value);                      } -                    keyboard::KeyCode::Left => { +                    keyboard::Key::Named(key::Named::ArrowLeft) => {                          if platform::is_jump_modifier_pressed(modifiers)                              && !is_secure                          { @@ -854,7 +823,7 @@ where                              state.cursor.move_left(value);                          }                      } -                    keyboard::KeyCode::Right => { +                    keyboard::Key::Named(key::Named::ArrowRight) => {                          if platform::is_jump_modifier_pressed(modifiers)                              && !is_secure                          { @@ -869,7 +838,7 @@ where                              state.cursor.move_right(value);                          }                      } -                    keyboard::KeyCode::Home => { +                    keyboard::Key::Named(key::Named::Home) => {                          if modifiers.shift() {                              state                                  .cursor @@ -878,7 +847,7 @@ where                              state.cursor.move_to(0);                          }                      } -                    keyboard::KeyCode::End => { +                    keyboard::Key::Named(key::Named::End) => {                          if modifiers.shift() {                              state.cursor.select_range(                                  state.cursor.start(value), @@ -888,7 +857,7 @@ where                              state.cursor.move_to(value.len());                          }                      } -                    keyboard::KeyCode::C +                    keyboard::Key::Character("c")                          if state.keyboard_modifiers.command() =>                      {                          if let Some((start, end)) = @@ -898,7 +867,7 @@ where                                  .write(value.select(start, end).to_string());                          }                      } -                    keyboard::KeyCode::X +                    keyboard::Key::Character("x")                          if state.keyboard_modifiers.command() =>                      {                          if let Some((start, end)) = @@ -916,7 +885,7 @@ where                          update_cache(state, value);                      } -                    keyboard::KeyCode::V => { +                    keyboard::Key::Character("v") => {                          if state.keyboard_modifiers.command()                              && !state.keyboard_modifiers.alt()                          { @@ -953,12 +922,12 @@ where                              state.is_pasting = None;                          }                      } -                    keyboard::KeyCode::A +                    keyboard::Key::Character("a")                          if state.keyboard_modifiers.command() =>                      {                          state.cursor.select_all(value);                      } -                    keyboard::KeyCode::Escape => { +                    keyboard::Key::Named(key::Named::Escape) => {                          state.is_focused = None;                          state.is_dragging = false;                          state.is_pasting = None; @@ -966,28 +935,55 @@ where                          state.keyboard_modifiers =                              keyboard::Modifiers::default();                      } -                    keyboard::KeyCode::Tab -                    | keyboard::KeyCode::Up -                    | keyboard::KeyCode::Down => { +                    keyboard::Key::Named( +                        key::Named::Tab +                        | key::Named::ArrowUp +                        | key::Named::ArrowDown, +                    ) => {                          return event::Status::Ignored;                      } -                    _ => {} +                    _ => { +                        if let Some(text) = text { +                            let c = text.chars().next().unwrap_or_default(); + +                            if state.is_pasting.is_none() +                                && !state.keyboard_modifiers.command() +                                && !c.is_control() +                            { +                                let mut editor = +                                    Editor::new(value, &mut state.cursor); + +                                editor.insert(c); + +                                let message = (on_input)(editor.contents()); +                                shell.publish(message); + +                                focus.updated_at = Instant::now(); + +                                update_cache(state, value); + +                                return event::Status::Captured; +                            } +                        } +                    }                  }                  return event::Status::Captured;              }          } -        Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => { +        Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {              let state = state();              if state.is_focused.is_some() { -                match key_code { -                    keyboard::KeyCode::V => { +                match key.as_ref() { +                    keyboard::Key::Character("v") => {                          state.is_pasting = None;                      } -                    keyboard::KeyCode::Tab -                    | keyboard::KeyCode::Up -                    | keyboard::KeyCode::Down => { +                    keyboard::Key::Named( +                        key::Named::Tab +                        | key::Named::ArrowUp +                        | key::Named::ArrowDown, +                    ) => {                          return event::Status::Ignored;                      }                      _ => {} @@ -1194,31 +1190,39 @@ pub fn draw<Renderer>(          (None, 0.0)      }; -    if let Some((cursor, color)) = cursor { -        renderer.with_translation(Vector::new(-offset, 0.0), |renderer| { -            renderer.fill_quad(cursor, color); -        }); +    let draw = |renderer: &mut Renderer, viewport| { +        if let Some((cursor, color)) = cursor { +            renderer.with_translation(Vector::new(-offset, 0.0), |renderer| { +                renderer.fill_quad(cursor, color); +            }); +        } else { +            renderer.with_translation(Vector::ZERO, |_| {}); +        } + +        renderer.fill_paragraph( +            if text.is_empty() { +                &state.placeholder +            } else { +                &state.value +            }, +            Point::new(text_bounds.x, text_bounds.center_y()) +                - Vector::new(offset, 0.0), +            if text.is_empty() { +                theme.placeholder_color(style) +            } else if is_disabled { +                theme.disabled_color(style) +            } else { +                theme.value_color(style) +            }, +            viewport, +        ); +    }; + +    if cursor.is_some() { +        renderer.with_layer(text_bounds, |renderer| draw(renderer, *viewport));      } else { -        renderer.with_translation(Vector::ZERO, |_| {}); +        draw(renderer, text_bounds);      } - -    renderer.fill_paragraph( -        if text.is_empty() { -            &state.placeholder -        } else { -            &state.value -        }, -        Point::new(text_bounds.x, text_bounds.center_y()) -            - Vector::new(offset, 0.0), -        if text.is_empty() { -            theme.placeholder_color(style) -        } else if is_disabled { -            theme.disabled_color(style) -        } else { -            theme.value_color(style) -        }, -        text_bounds, -    );  }  /// Computes the current [`mouse::Interaction`] of the [`TextInput`]. diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index d8723080..941159ea 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -168,12 +168,11 @@ where          tree::State::new(widget::text::State::<Renderer::Paragraph>::default())      } -    fn width(&self) -> Length { -        self.width -    } - -    fn height(&self) -> Length { -        Length::Shrink +    fn size(&self) -> Size<Length> { +        Size { +            width: self.width, +            height: Length::Shrink, +        }      }      fn layout( diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 9e102c56..d09a9255 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -64,6 +64,12 @@ where          self      } +    /// Sets the [`text::Shaping`] strategy of the [`Tooltip`]. +    pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { +        self.tooltip = self.tooltip.shaping(shaping); +        self +    } +      /// Sets the font of the [`Tooltip`].      ///      /// [`Font`]: Renderer::Font @@ -125,12 +131,8 @@ where          widget::tree::Tag::of::<State>()      } -    fn width(&self) -> Length { -        self.content.as_widget().width() -    } - -    fn height(&self) -> Length { -        self.content.as_widget().height() +    fn size(&self) -> Size<Length> { +        self.content.as_widget().size()      }      fn layout( @@ -347,7 +349,7 @@ where                      .then(|| viewport.size())                      .unwrap_or(Size::INFINITY),              ) -            .pad(Padding::new(self.padding)), +            .shrink(Padding::new(self.padding)),          );          let text_bounds = text_layout.bounds(); diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 01d3359c..a3029d76 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -156,12 +156,11 @@ where          tree::State::new(State::new())      } -    fn width(&self) -> Length { -        Length::Shrink -    } - -    fn height(&self) -> Length { -        self.height +    fn size(&self) -> Size<Length> { +        Size { +            width: Length::Shrink, +            height: self.height, +        }      }      fn layout( @@ -170,10 +169,7 @@ where          _renderer: &Renderer,          limits: &layout::Limits,      ) -> layout::Node { -        let limits = limits.width(self.width).height(self.height); -        let size = limits.resolve(Size::ZERO); - -        layout::Node::new(size) +        layout::atomic(limits, self.width, self.height)      }      fn on_event( diff --git a/winit/src/application.rs b/winit/src/application.rs index d9700075..bf48538d 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -115,7 +115,9 @@ where      let mut debug = Debug::new();      debug.startup_started(); -    let event_loop = EventLoopBuilder::with_user_event().build(); +    let event_loop = EventLoopBuilder::with_user_event() +        .build() +        .expect("Create event loop");      let proxy = event_loop.create_proxy();      let runtime = { @@ -155,7 +157,7 @@ where      {          use winit::platform::web::WindowExtWebSys; -        let canvas = window.canvas(); +        let canvas = window.canvas().expect("Get window canvas");          let window = web_sys::window().unwrap();          let document = window.document().unwrap(); @@ -210,45 +212,28 @@ where      let mut context = task::Context::from_waker(task::noop_waker_ref()); -    platform::run(event_loop, move |event, _, control_flow| { -        use winit::event_loop::ControlFlow; - -        if let ControlFlow::ExitWithCode(_) = control_flow { +    let _ = event_loop.run(move |event, event_loop| { +        if event_loop.exiting() {              return;          } -        let event = match event { -            winit::event::Event::WindowEvent { -                event: -                    winit::event::WindowEvent::ScaleFactorChanged { -                        new_inner_size, -                        .. -                    }, -                window_id, -            } => Some(winit::event::Event::WindowEvent { -                event: winit::event::WindowEvent::Resized(*new_inner_size), -                window_id, -            }), -            _ => event.to_static(), -        }; - -        if let Some(event) = event { -            event_sender.start_send(event).expect("Send event"); +        event_sender.start_send(event).expect("Send event"); -            let poll = instance.as_mut().poll(&mut context); +        let poll = instance.as_mut().poll(&mut context); -            match poll { -                task::Poll::Pending => { -                    if let Ok(Some(flow)) = control_receiver.try_next() { -                        *control_flow = flow; -                    } -                } -                task::Poll::Ready(_) => { -                    *control_flow = ControlFlow::Exit; +        match poll { +            task::Poll::Pending => { +                if let Ok(Some(flow)) = control_receiver.try_next() { +                    event_loop.set_control_flow(flow);                  } -            }; -        } -    }) +            } +            task::Poll::Ready(_) => { +                event_loop.exit(); +            } +        }; +    }); + +    Ok(())  }  async fn run_instance<A, E, C>( @@ -259,7 +244,7 @@ async fn run_instance<A, E, C>(      mut proxy: winit::event_loop::EventLoopProxy<A::Message>,      mut debug: Debug,      mut event_receiver: mpsc::UnboundedReceiver< -        winit::event::Event<'_, A::Message>, +        winit::event::Event<A::Message>,      >,      mut control_sender: mpsc::UnboundedSender<winit::event_loop::ControlFlow>,      init_command: Command<A::Message>, @@ -327,77 +312,56 @@ async fn run_instance<A, E, C>(      while let Some(event) = event_receiver.next().await {          match event { -            event::Event::NewEvents(start_cause) => { -                redraw_pending = matches!( -                    start_cause, -                    event::StartCause::Init -                        | event::StartCause::Poll -                        | event::StartCause::ResumeTimeReached { .. } -                ); +            event::Event::NewEvents( +                event::StartCause::Init +                | event::StartCause::ResumeTimeReached { .. }, +            ) if !redraw_pending => { +                window.request_redraw(); +                redraw_pending = true;              } -            event::Event::MainEventsCleared => { -                if !redraw_pending && events.is_empty() && messages.is_empty() { -                    continue; -                } +            event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( +                event::MacOS::ReceivedUrl(url), +            )) => { +                use crate::core::event; -                debug.event_processing_started(); +                events.push(Event::PlatformSpecific( +                    event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( +                        url, +                    )), +                )); +            } +            event::Event::UserEvent(message) => { +                messages.push(message); +            } +            event::Event::WindowEvent { +                event: event::WindowEvent::RedrawRequested { .. }, +                .. +            } => { +                let physical_size = state.physical_size(); -                let (interface_state, statuses) = user_interface.update( -                    &events, -                    state.cursor(), -                    &mut renderer, -                    &mut clipboard, -                    &mut messages, -                ); +                if physical_size.width == 0 || physical_size.height == 0 { +                    continue; +                } -                debug.event_processing_finished(); +                let current_viewport_version = state.viewport_version(); -                for (event, status) in -                    events.drain(..).zip(statuses.into_iter()) -                { -                    runtime.broadcast(event, status); -                } +                if viewport_version != current_viewport_version { +                    let logical_size = state.logical_size(); -                if !messages.is_empty() -                    || matches!( -                        interface_state, -                        user_interface::State::Outdated -                    ) -                { -                    let mut cache = -                        ManuallyDrop::into_inner(user_interface).into_cache(); +                    debug.layout_started(); +                    user_interface = ManuallyDrop::new( +                        ManuallyDrop::into_inner(user_interface) +                            .relayout(logical_size, &mut renderer), +                    ); +                    debug.layout_finished(); -                    // Update application -                    update( -                        &mut application, -                        &mut compositor, +                    compositor.configure_surface(                          &mut surface, -                        &mut cache, -                        &state, -                        &mut renderer, -                        &mut runtime, -                        &mut clipboard, -                        &mut should_exit, -                        &mut proxy, -                        &mut debug, -                        &mut messages, -                        &window, +                        physical_size.width, +                        physical_size.height,                      ); -                    // Update window -                    state.synchronize(&application, &window); - -                    user_interface = ManuallyDrop::new(build_user_interface( -                        &application, -                        cache, -                        &mut renderer, -                        state.logical_size(), -                        &mut debug, -                    )); - -                    if should_exit { -                        break; -                    } +                    viewport_version = current_viewport_version;                  }                  // TODO: Avoid redrawing all the time by forcing widgets to @@ -418,6 +382,24 @@ async fn run_instance<A, E, C>(                      &mut messages,                  ); +                let _ = control_sender.start_send(match interface_state { +                    user_interface::State::Updated { +                        redraw_request: Some(redraw_request), +                    } => match redraw_request { +                        window::RedrawRequest::NextFrame => { +                            window.request_redraw(); + +                            ControlFlow::Wait +                        } +                        window::RedrawRequest::At(at) => { +                            ControlFlow::WaitUntil(at) +                        } +                    }, +                    _ => ControlFlow::Wait, +                }); + +                runtime.broadcast(redraw_event, core::event::Status::Ignored); +                  debug.draw_started();                  let new_mouse_interaction = user_interface.draw(                      &mut renderer, @@ -427,6 +409,7 @@ async fn run_instance<A, E, C>(                      },                      state.cursor(),                  ); +                redraw_pending = false;                  debug.draw_finished();                  if new_mouse_interaction != mouse_interaction { @@ -437,85 +420,7 @@ async fn run_instance<A, E, C>(                      mouse_interaction = new_mouse_interaction;                  } -                window.request_redraw(); -                runtime.broadcast(redraw_event, core::event::Status::Ignored); - -                let _ = control_sender.start_send(match interface_state { -                    user_interface::State::Updated { -                        redraw_request: Some(redraw_request), -                    } => match redraw_request { -                        window::RedrawRequest::NextFrame => ControlFlow::Poll, -                        window::RedrawRequest::At(at) => { -                            ControlFlow::WaitUntil(at) -                        } -                    }, -                    _ => ControlFlow::Wait, -                }); - -                redraw_pending = false; -            } -            event::Event::PlatformSpecific(event::PlatformSpecific::MacOS( -                event::MacOS::ReceivedUrl(url), -            )) => { -                use crate::core::event; - -                events.push(Event::PlatformSpecific( -                    event::PlatformSpecific::MacOS(event::MacOS::ReceivedUrl( -                        url, -                    )), -                )); -            } -            event::Event::UserEvent(message) => { -                messages.push(message); -            } -            event::Event::RedrawRequested(_) => { -                let physical_size = state.physical_size(); - -                if physical_size.width == 0 || physical_size.height == 0 { -                    continue; -                } -                  debug.render_started(); -                let current_viewport_version = state.viewport_version(); - -                if viewport_version != current_viewport_version { -                    let logical_size = state.logical_size(); - -                    debug.layout_started(); -                    user_interface = ManuallyDrop::new( -                        ManuallyDrop::into_inner(user_interface) -                            .relayout(logical_size, &mut renderer), -                    ); -                    debug.layout_finished(); - -                    debug.draw_started(); -                    let new_mouse_interaction = user_interface.draw( -                        &mut renderer, -                        state.theme(), -                        &renderer::Style { -                            text_color: state.text_color(), -                        }, -                        state.cursor(), -                    ); - -                    if new_mouse_interaction != mouse_interaction { -                        window.set_cursor_icon(conversion::mouse_interaction( -                            new_mouse_interaction, -                        )); - -                        mouse_interaction = new_mouse_interaction; -                    } -                    debug.draw_finished(); - -                    compositor.configure_surface( -                        &mut surface, -                        physical_size.width, -                        physical_size.height, -                    ); - -                    viewport_version = current_viewport_version; -                } -                  match compositor.present(                      &mut renderer,                      &mut surface, @@ -557,13 +462,80 @@ async fn run_instance<A, E, C>(                  if let Some(event) = conversion::window_event(                      window::Id::MAIN, -                    &window_event, +                    window_event,                      state.scale_factor(),                      state.modifiers(),                  ) {                      events.push(event);                  }              } +            event::Event::AboutToWait => { +                if events.is_empty() && messages.is_empty() { +                    continue; +                } + +                debug.event_processing_started(); + +                let (interface_state, statuses) = user_interface.update( +                    &events, +                    state.cursor(), +                    &mut renderer, +                    &mut clipboard, +                    &mut messages, +                ); + +                debug.event_processing_finished(); + +                for (event, status) in +                    events.drain(..).zip(statuses.into_iter()) +                { +                    runtime.broadcast(event, status); +                } + +                if !messages.is_empty() +                    || matches!( +                        interface_state, +                        user_interface::State::Outdated +                    ) +                { +                    let mut cache = +                        ManuallyDrop::into_inner(user_interface).into_cache(); + +                    // Update application +                    update( +                        &mut application, +                        &mut compositor, +                        &mut surface, +                        &mut cache, +                        &mut state, +                        &mut renderer, +                        &mut runtime, +                        &mut clipboard, +                        &mut should_exit, +                        &mut proxy, +                        &mut debug, +                        &mut messages, +                        &window, +                    ); + +                    user_interface = ManuallyDrop::new(build_user_interface( +                        &application, +                        cache, +                        &mut renderer, +                        state.logical_size(), +                        &mut debug, +                    )); + +                    if should_exit { +                        break; +                    } +                } + +                if !redraw_pending { +                    window.request_redraw(); +                    redraw_pending = true; +                } +            }              _ => {}          }      } @@ -575,8 +547,8 @@ async fn run_instance<A, E, C>(  /// Returns true if the provided event should cause an [`Application`] to  /// exit.  pub fn requests_exit( -    event: &winit::event::WindowEvent<'_>, -    _modifiers: winit::event::ModifiersState, +    event: &winit::event::WindowEvent, +    _modifiers: winit::keyboard::ModifiersState,  ) -> bool {      use winit::event::WindowEvent; @@ -584,14 +556,14 @@ pub fn requests_exit(          WindowEvent::CloseRequested => true,          #[cfg(target_os = "macos")]          WindowEvent::KeyboardInput { -            input: -                winit::event::KeyboardInput { -                    virtual_keycode: Some(winit::event::VirtualKeyCode::Q), +            event: +                winit::event::KeyEvent { +                    logical_key: winit::keyboard::Key::Character(c),                      state: winit::event::ElementState::Pressed,                      ..                  },              .. -        } if _modifiers.logo() => true, +        } if c == "q" && _modifiers.super_key() => true,          _ => false,      }  } @@ -626,7 +598,7 @@ pub fn update<A: Application, C, E: Executor>(      compositor: &mut C,      surface: &mut C::Surface,      cache: &mut user_interface::Cache, -    state: &State<A>, +    state: &mut State<A>,      renderer: &mut A::Renderer,      runtime: &mut Runtime<E, Proxy<A::Message>, A::Message>,      clipboard: &mut Clipboard, @@ -663,6 +635,8 @@ pub fn update<A: Application, C, E: Executor>(          );      } +    state.synchronize(application, window); +      let subscription = application.subscription();      runtime.track(subscription.into_recipes());  } @@ -726,10 +700,11 @@ pub fn run_command<A, C, E>(                      );                  }                  window::Action::Resize(_id, size) => { -                    window.set_inner_size(winit::dpi::LogicalSize { -                        width: size.width, -                        height: size.height, -                    }); +                    let _ = +                        window.request_inner_size(winit::dpi::LogicalSize { +                            width: size.width, +                            height: size.height, +                        });                  }                  window::Action::FetchSize(_id, callback) => {                      let size = @@ -742,9 +717,19 @@ pub fn run_command<A, C, E>(                          )))                          .expect("Send message to event loop");                  } +                window::Action::FetchMaximized(_id, callback) => { +                    proxy +                        .send_event(callback(window.is_maximized())) +                        .expect("Send message to event loop"); +                }                  window::Action::Maximize(_id, maximized) => {                      window.set_maximized(maximized);                  } +                window::Action::FetchMinimized(_id, callback) => { +                    proxy +                        .send_event(callback(window.is_minimized())) +                        .expect("Send message to event loop"); +                }                  window::Action::Minimize(_id, minimized) => {                      window.set_minimized(minimized);                  } @@ -878,43 +863,3 @@ pub fn run_command<A, C, E>(          }      }  } - -#[cfg(not(target_arch = "wasm32"))] -mod platform { -    pub fn run<T, F>( -        mut event_loop: winit::event_loop::EventLoop<T>, -        event_handler: F, -    ) -> Result<(), super::Error> -    where -        F: 'static -            + FnMut( -                winit::event::Event<'_, T>, -                &winit::event_loop::EventLoopWindowTarget<T>, -                &mut winit::event_loop::ControlFlow, -            ), -    { -        use winit::platform::run_return::EventLoopExtRunReturn; - -        let _ = event_loop.run_return(event_handler); - -        Ok(()) -    } -} - -#[cfg(target_arch = "wasm32")] -mod platform { -    pub fn run<T, F>( -        event_loop: winit::event_loop::EventLoop<T>, -        event_handler: F, -    ) -> ! -    where -        F: 'static -            + FnMut( -                winit::event::Event<'_, T>, -                &winit::event_loop::EventLoopWindowTarget<T>, -                &mut winit::event_loop::ControlFlow, -            ), -    { -        event_loop.run(event_handler) -    } -} diff --git a/winit/src/application/state.rs b/winit/src/application/state.rs index e655529a..8c9b20e0 100644 --- a/winit/src/application/state.rs +++ b/winit/src/application/state.rs @@ -22,7 +22,7 @@ where      viewport: Viewport,      viewport_version: usize,      cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, -    modifiers: winit::event::ModifiersState, +    modifiers: winit::keyboard::ModifiersState,      theme: <A::Renderer as core::Renderer>::Theme,      appearance: application::Appearance,      application: PhantomData<A>, @@ -54,7 +54,7 @@ where              viewport,              viewport_version: 0,              cursor_position: None, -            modifiers: winit::event::ModifiersState::default(), +            modifiers: winit::keyboard::ModifiersState::default(),              theme,              appearance,              application: PhantomData, @@ -102,7 +102,7 @@ where      }      /// Returns the current keyboard modifiers of the [`State`]. -    pub fn modifiers(&self) -> winit::event::ModifiersState { +    pub fn modifiers(&self) -> winit::keyboard::ModifiersState {          self.modifiers      } @@ -126,7 +126,7 @@ where      pub fn update(          &mut self,          window: &Window, -        event: &WindowEvent<'_>, +        event: &WindowEvent,          _debug: &mut Debug,      ) {          match event { @@ -142,10 +142,9 @@ where              }              WindowEvent::ScaleFactorChanged {                  scale_factor: new_scale_factor, -                new_inner_size, +                ..              } => { -                let size = -                    Size::new(new_inner_size.width, new_inner_size.height); +                let size = self.viewport.physical_size();                  self.viewport = Viewport::with_physical_size(                      size, @@ -164,13 +163,16 @@ where                  self.cursor_position = None;              }              WindowEvent::ModifiersChanged(new_modifiers) => { -                self.modifiers = *new_modifiers; +                self.modifiers = new_modifiers.state();              }              #[cfg(feature = "debug")]              WindowEvent::KeyboardInput { -                input: -                    winit::event::KeyboardInput { -                        virtual_keycode: Some(winit::event::VirtualKeyCode::F12), +                event: +                    winit::event::KeyEvent { +                        logical_key: +                            winit::keyboard::Key::Named( +                                winit::keyboard::NamedKey::F12, +                            ),                          state: winit::event::ElementState::Pressed,                          ..                      }, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index 7e51a2d4..90a5d27f 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -128,9 +128,9 @@ pub fn window_settings(  /// Converts a winit window event into an iced event.  pub fn window_event(      id: window::Id, -    event: &winit::event::WindowEvent<'_>, +    event: winit::event::WindowEvent,      scale_factor: f64, -    modifiers: winit::event::ModifiersState, +    modifiers: winit::keyboard::ModifiersState,  ) -> Option<Event> {      use winit::event::WindowEvent; @@ -146,17 +146,6 @@ pub fn window_event(                  },              ))          } -        WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { -            let logical_size = new_inner_size.to_logical(scale_factor); - -            Some(Event::Window( -                id, -                window::Event::Resized { -                    width: logical_size.width, -                    height: logical_size.height, -                }, -            )) -        }          WindowEvent::CloseRequested => {              Some(Event::Window(id, window::Event::CloseRequested))          } @@ -174,7 +163,7 @@ pub fn window_event(              Some(Event::Mouse(mouse::Event::CursorLeft))          }          WindowEvent::MouseInput { button, state, .. } => { -            let button = mouse_button(*button); +            let button = mouse_button(button);              Some(Event::Mouse(match state {                  winit::event::ElementState::Pressed => { @@ -189,8 +178,8 @@ pub fn window_event(              winit::event::MouseScrollDelta::LineDelta(delta_x, delta_y) => {                  Some(Event::Mouse(mouse::Event::WheelScrolled {                      delta: mouse::ScrollDelta::Lines { -                        x: *delta_x, -                        y: *delta_y, +                        x: delta_x, +                        y: delta_y,                      },                  }))              } @@ -203,42 +192,59 @@ pub fn window_event(                  }))              }          }, -        WindowEvent::ReceivedCharacter(c) if !is_private_use_character(*c) => { -            Some(Event::Keyboard(keyboard::Event::CharacterReceived(*c))) -        }          WindowEvent::KeyboardInput { -            input: -                winit::event::KeyboardInput { -                    virtual_keycode: Some(virtual_keycode), +            event: +                winit::event::KeyEvent { +                    logical_key,                      state, +                    text, +                    location,                      ..                  },              ..          } => Some(Event::Keyboard({ -            let key_code = key_code(*virtual_keycode); +            let key = key(logical_key);              let modifiers = self::modifiers(modifiers); +            let location = match location { +                winit::keyboard::KeyLocation::Standard => { +                    keyboard::Location::Standard +                } +                winit::keyboard::KeyLocation::Left => keyboard::Location::Left, +                winit::keyboard::KeyLocation::Right => { +                    keyboard::Location::Right +                } +                winit::keyboard::KeyLocation::Numpad => { +                    keyboard::Location::Numpad +                } +            }; +              match state {                  winit::event::ElementState::Pressed => {                      keyboard::Event::KeyPressed { -                        key_code, +                        key,                          modifiers, +                        location, +                        text,                      }                  }                  winit::event::ElementState::Released => {                      keyboard::Event::KeyReleased { -                        key_code, +                        key,                          modifiers, +                        location,                      }                  }              }          })), -        WindowEvent::ModifiersChanged(new_modifiers) => Some(Event::Keyboard( -            keyboard::Event::ModifiersChanged(self::modifiers(*new_modifiers)), -        )), +        WindowEvent::ModifiersChanged(new_modifiers) => { +            Some(Event::Keyboard(keyboard::Event::ModifiersChanged( +                self::modifiers(new_modifiers.state()), +            ))) +        }          WindowEvent::Focused(focused) => Some(Event::Window(              id, -            if *focused { +            if focused {                  window::Event::Focused              } else {                  window::Event::Unfocused @@ -254,7 +260,7 @@ pub fn window_event(              Some(Event::Window(id, window::Event::FilesHoveredLeft))          }          WindowEvent::Touch(touch) => { -            Some(Event::Touch(touch_event(*touch, scale_factor))) +            Some(Event::Touch(touch_event(touch, scale_factor)))          }          WindowEvent::Moved(position) => {              let winit::dpi::LogicalPosition { x, y } = @@ -365,7 +371,7 @@ pub fn mouse_interaction(      match interaction {          Interaction::Idle => winit::window::CursorIcon::Default, -        Interaction::Pointer => winit::window::CursorIcon::Hand, +        Interaction::Pointer => winit::window::CursorIcon::Pointer,          Interaction::Working => winit::window::CursorIcon::Progress,          Interaction::Grab => winit::window::CursorIcon::Grab,          Interaction::Grabbing => winit::window::CursorIcon::Grabbing, @@ -388,6 +394,8 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {          winit::event::MouseButton::Left => mouse::Button::Left,          winit::event::MouseButton::Right => mouse::Button::Right,          winit::event::MouseButton::Middle => mouse::Button::Middle, +        winit::event::MouseButton::Back => mouse::Button::Back, +        winit::event::MouseButton::Forward => mouse::Button::Forward,          winit::event::MouseButton::Other(other) => mouse::Button::Other(other),      }  } @@ -398,14 +406,14 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {  /// [`winit`]: https://github.com/rust-windowing/winit  /// [`iced`]: https://github.com/iced-rs/iced/tree/0.10  pub fn modifiers( -    modifiers: winit::event::ModifiersState, +    modifiers: winit::keyboard::ModifiersState,  ) -> keyboard::Modifiers {      let mut result = keyboard::Modifiers::empty(); -    result.set(keyboard::Modifiers::SHIFT, modifiers.shift()); -    result.set(keyboard::Modifiers::CTRL, modifiers.ctrl()); -    result.set(keyboard::Modifiers::ALT, modifiers.alt()); -    result.set(keyboard::Modifiers::LOGO, modifiers.logo()); +    result.set(keyboard::Modifiers::SHIFT, modifiers.shift_key()); +    result.set(keyboard::Modifiers::CTRL, modifiers.control_key()); +    result.set(keyboard::Modifiers::ALT, modifiers.alt_key()); +    result.set(keyboard::Modifiers::LOGO, modifiers.super_key());      result  } @@ -455,179 +463,328 @@ pub fn touch_event(  ///  /// [`winit`]: https://github.com/rust-windowing/winit  /// [`iced`]: https://github.com/iced-rs/iced/tree/0.10 -pub fn key_code( -    virtual_keycode: winit::event::VirtualKeyCode, -) -> keyboard::KeyCode { -    use keyboard::KeyCode; - -    match virtual_keycode { -        winit::event::VirtualKeyCode::Key1 => KeyCode::Key1, -        winit::event::VirtualKeyCode::Key2 => KeyCode::Key2, -        winit::event::VirtualKeyCode::Key3 => KeyCode::Key3, -        winit::event::VirtualKeyCode::Key4 => KeyCode::Key4, -        winit::event::VirtualKeyCode::Key5 => KeyCode::Key5, -        winit::event::VirtualKeyCode::Key6 => KeyCode::Key6, -        winit::event::VirtualKeyCode::Key7 => KeyCode::Key7, -        winit::event::VirtualKeyCode::Key8 => KeyCode::Key8, -        winit::event::VirtualKeyCode::Key9 => KeyCode::Key9, -        winit::event::VirtualKeyCode::Key0 => KeyCode::Key0, -        winit::event::VirtualKeyCode::A => KeyCode::A, -        winit::event::VirtualKeyCode::B => KeyCode::B, -        winit::event::VirtualKeyCode::C => KeyCode::C, -        winit::event::VirtualKeyCode::D => KeyCode::D, -        winit::event::VirtualKeyCode::E => KeyCode::E, -        winit::event::VirtualKeyCode::F => KeyCode::F, -        winit::event::VirtualKeyCode::G => KeyCode::G, -        winit::event::VirtualKeyCode::H => KeyCode::H, -        winit::event::VirtualKeyCode::I => KeyCode::I, -        winit::event::VirtualKeyCode::J => KeyCode::J, -        winit::event::VirtualKeyCode::K => KeyCode::K, -        winit::event::VirtualKeyCode::L => KeyCode::L, -        winit::event::VirtualKeyCode::M => KeyCode::M, -        winit::event::VirtualKeyCode::N => KeyCode::N, -        winit::event::VirtualKeyCode::O => KeyCode::O, -        winit::event::VirtualKeyCode::P => KeyCode::P, -        winit::event::VirtualKeyCode::Q => KeyCode::Q, -        winit::event::VirtualKeyCode::R => KeyCode::R, -        winit::event::VirtualKeyCode::S => KeyCode::S, -        winit::event::VirtualKeyCode::T => KeyCode::T, -        winit::event::VirtualKeyCode::U => KeyCode::U, -        winit::event::VirtualKeyCode::V => KeyCode::V, -        winit::event::VirtualKeyCode::W => KeyCode::W, -        winit::event::VirtualKeyCode::X => KeyCode::X, -        winit::event::VirtualKeyCode::Y => KeyCode::Y, -        winit::event::VirtualKeyCode::Z => KeyCode::Z, -        winit::event::VirtualKeyCode::Escape => KeyCode::Escape, -        winit::event::VirtualKeyCode::F1 => KeyCode::F1, -        winit::event::VirtualKeyCode::F2 => KeyCode::F2, -        winit::event::VirtualKeyCode::F3 => KeyCode::F3, -        winit::event::VirtualKeyCode::F4 => KeyCode::F4, -        winit::event::VirtualKeyCode::F5 => KeyCode::F5, -        winit::event::VirtualKeyCode::F6 => KeyCode::F6, -        winit::event::VirtualKeyCode::F7 => KeyCode::F7, -        winit::event::VirtualKeyCode::F8 => KeyCode::F8, -        winit::event::VirtualKeyCode::F9 => KeyCode::F9, -        winit::event::VirtualKeyCode::F10 => KeyCode::F10, -        winit::event::VirtualKeyCode::F11 => KeyCode::F11, -        winit::event::VirtualKeyCode::F12 => KeyCode::F12, -        winit::event::VirtualKeyCode::F13 => KeyCode::F13, -        winit::event::VirtualKeyCode::F14 => KeyCode::F14, -        winit::event::VirtualKeyCode::F15 => KeyCode::F15, -        winit::event::VirtualKeyCode::F16 => KeyCode::F16, -        winit::event::VirtualKeyCode::F17 => KeyCode::F17, -        winit::event::VirtualKeyCode::F18 => KeyCode::F18, -        winit::event::VirtualKeyCode::F19 => KeyCode::F19, -        winit::event::VirtualKeyCode::F20 => KeyCode::F20, -        winit::event::VirtualKeyCode::F21 => KeyCode::F21, -        winit::event::VirtualKeyCode::F22 => KeyCode::F22, -        winit::event::VirtualKeyCode::F23 => KeyCode::F23, -        winit::event::VirtualKeyCode::F24 => KeyCode::F24, -        winit::event::VirtualKeyCode::Snapshot => KeyCode::Snapshot, -        winit::event::VirtualKeyCode::Scroll => KeyCode::Scroll, -        winit::event::VirtualKeyCode::Pause => KeyCode::Pause, -        winit::event::VirtualKeyCode::Insert => KeyCode::Insert, -        winit::event::VirtualKeyCode::Home => KeyCode::Home, -        winit::event::VirtualKeyCode::Delete => KeyCode::Delete, -        winit::event::VirtualKeyCode::End => KeyCode::End, -        winit::event::VirtualKeyCode::PageDown => KeyCode::PageDown, -        winit::event::VirtualKeyCode::PageUp => KeyCode::PageUp, -        winit::event::VirtualKeyCode::Left => KeyCode::Left, -        winit::event::VirtualKeyCode::Up => KeyCode::Up, -        winit::event::VirtualKeyCode::Right => KeyCode::Right, -        winit::event::VirtualKeyCode::Down => KeyCode::Down, -        winit::event::VirtualKeyCode::Back => KeyCode::Backspace, -        winit::event::VirtualKeyCode::Return => KeyCode::Enter, -        winit::event::VirtualKeyCode::Space => KeyCode::Space, -        winit::event::VirtualKeyCode::Compose => KeyCode::Compose, -        winit::event::VirtualKeyCode::Caret => KeyCode::Caret, -        winit::event::VirtualKeyCode::Numlock => KeyCode::Numlock, -        winit::event::VirtualKeyCode::Numpad0 => KeyCode::Numpad0, -        winit::event::VirtualKeyCode::Numpad1 => KeyCode::Numpad1, -        winit::event::VirtualKeyCode::Numpad2 => KeyCode::Numpad2, -        winit::event::VirtualKeyCode::Numpad3 => KeyCode::Numpad3, -        winit::event::VirtualKeyCode::Numpad4 => KeyCode::Numpad4, -        winit::event::VirtualKeyCode::Numpad5 => KeyCode::Numpad5, -        winit::event::VirtualKeyCode::Numpad6 => KeyCode::Numpad6, -        winit::event::VirtualKeyCode::Numpad7 => KeyCode::Numpad7, -        winit::event::VirtualKeyCode::Numpad8 => KeyCode::Numpad8, -        winit::event::VirtualKeyCode::Numpad9 => KeyCode::Numpad9, -        winit::event::VirtualKeyCode::AbntC1 => KeyCode::AbntC1, -        winit::event::VirtualKeyCode::AbntC2 => KeyCode::AbntC2, -        winit::event::VirtualKeyCode::NumpadAdd => KeyCode::NumpadAdd, -        winit::event::VirtualKeyCode::Plus => KeyCode::Plus, -        winit::event::VirtualKeyCode::Apostrophe => KeyCode::Apostrophe, -        winit::event::VirtualKeyCode::Apps => KeyCode::Apps, -        winit::event::VirtualKeyCode::At => KeyCode::At, -        winit::event::VirtualKeyCode::Ax => KeyCode::Ax, -        winit::event::VirtualKeyCode::Backslash => KeyCode::Backslash, -        winit::event::VirtualKeyCode::Calculator => KeyCode::Calculator, -        winit::event::VirtualKeyCode::Capital => KeyCode::Capital, -        winit::event::VirtualKeyCode::Colon => KeyCode::Colon, -        winit::event::VirtualKeyCode::Comma => KeyCode::Comma, -        winit::event::VirtualKeyCode::Convert => KeyCode::Convert, -        winit::event::VirtualKeyCode::NumpadDecimal => KeyCode::NumpadDecimal, -        winit::event::VirtualKeyCode::NumpadDivide => KeyCode::NumpadDivide, -        winit::event::VirtualKeyCode::Equals => KeyCode::Equals, -        winit::event::VirtualKeyCode::Grave => KeyCode::Grave, -        winit::event::VirtualKeyCode::Kana => KeyCode::Kana, -        winit::event::VirtualKeyCode::Kanji => KeyCode::Kanji, -        winit::event::VirtualKeyCode::LAlt => KeyCode::LAlt, -        winit::event::VirtualKeyCode::LBracket => KeyCode::LBracket, -        winit::event::VirtualKeyCode::LControl => KeyCode::LControl, -        winit::event::VirtualKeyCode::LShift => KeyCode::LShift, -        winit::event::VirtualKeyCode::LWin => KeyCode::LWin, -        winit::event::VirtualKeyCode::Mail => KeyCode::Mail, -        winit::event::VirtualKeyCode::MediaSelect => KeyCode::MediaSelect, -        winit::event::VirtualKeyCode::MediaStop => KeyCode::MediaStop, -        winit::event::VirtualKeyCode::Minus => KeyCode::Minus, -        winit::event::VirtualKeyCode::NumpadMultiply => KeyCode::NumpadMultiply, -        winit::event::VirtualKeyCode::Mute => KeyCode::Mute, -        winit::event::VirtualKeyCode::MyComputer => KeyCode::MyComputer, -        winit::event::VirtualKeyCode::NavigateForward => { -            KeyCode::NavigateForward -        } -        winit::event::VirtualKeyCode::NavigateBackward => { -            KeyCode::NavigateBackward +pub fn key(key: winit::keyboard::Key) -> keyboard::Key { +    use keyboard::key::Named; +    use winit::keyboard::NamedKey; + +    match key { +        winit::keyboard::Key::Character(c) => keyboard::Key::Character(c), +        winit::keyboard::Key::Named(named_key) => { +            keyboard::Key::Named(match named_key { +                NamedKey::Alt => Named::Alt, +                NamedKey::AltGraph => Named::AltGraph, +                NamedKey::CapsLock => Named::CapsLock, +                NamedKey::Control => Named::Control, +                NamedKey::Fn => Named::Fn, +                NamedKey::FnLock => Named::FnLock, +                NamedKey::NumLock => Named::NumLock, +                NamedKey::ScrollLock => Named::ScrollLock, +                NamedKey::Shift => Named::Shift, +                NamedKey::Symbol => Named::Symbol, +                NamedKey::SymbolLock => Named::SymbolLock, +                NamedKey::Meta => Named::Meta, +                NamedKey::Hyper => Named::Hyper, +                NamedKey::Super => Named::Super, +                NamedKey::Enter => Named::Enter, +                NamedKey::Tab => Named::Tab, +                NamedKey::Space => Named::Space, +                NamedKey::ArrowDown => Named::ArrowDown, +                NamedKey::ArrowLeft => Named::ArrowLeft, +                NamedKey::ArrowRight => Named::ArrowRight, +                NamedKey::ArrowUp => Named::ArrowUp, +                NamedKey::End => Named::End, +                NamedKey::Home => Named::Home, +                NamedKey::PageDown => Named::PageDown, +                NamedKey::PageUp => Named::PageUp, +                NamedKey::Backspace => Named::Backspace, +                NamedKey::Clear => Named::Clear, +                NamedKey::Copy => Named::Copy, +                NamedKey::CrSel => Named::CrSel, +                NamedKey::Cut => Named::Cut, +                NamedKey::Delete => Named::Delete, +                NamedKey::EraseEof => Named::EraseEof, +                NamedKey::ExSel => Named::ExSel, +                NamedKey::Insert => Named::Insert, +                NamedKey::Paste => Named::Paste, +                NamedKey::Redo => Named::Redo, +                NamedKey::Undo => Named::Undo, +                NamedKey::Accept => Named::Accept, +                NamedKey::Again => Named::Again, +                NamedKey::Attn => Named::Attn, +                NamedKey::Cancel => Named::Cancel, +                NamedKey::ContextMenu => Named::ContextMenu, +                NamedKey::Escape => Named::Escape, +                NamedKey::Execute => Named::Execute, +                NamedKey::Find => Named::Find, +                NamedKey::Help => Named::Help, +                NamedKey::Pause => Named::Pause, +                NamedKey::Play => Named::Play, +                NamedKey::Props => Named::Props, +                NamedKey::Select => Named::Select, +                NamedKey::ZoomIn => Named::ZoomIn, +                NamedKey::ZoomOut => Named::ZoomOut, +                NamedKey::BrightnessDown => Named::BrightnessDown, +                NamedKey::BrightnessUp => Named::BrightnessUp, +                NamedKey::Eject => Named::Eject, +                NamedKey::LogOff => Named::LogOff, +                NamedKey::Power => Named::Power, +                NamedKey::PowerOff => Named::PowerOff, +                NamedKey::PrintScreen => Named::PrintScreen, +                NamedKey::Hibernate => Named::Hibernate, +                NamedKey::Standby => Named::Standby, +                NamedKey::WakeUp => Named::WakeUp, +                NamedKey::AllCandidates => Named::AllCandidates, +                NamedKey::Alphanumeric => Named::Alphanumeric, +                NamedKey::CodeInput => Named::CodeInput, +                NamedKey::Compose => Named::Compose, +                NamedKey::Convert => Named::Convert, +                NamedKey::FinalMode => Named::FinalMode, +                NamedKey::GroupFirst => Named::GroupFirst, +                NamedKey::GroupLast => Named::GroupLast, +                NamedKey::GroupNext => Named::GroupNext, +                NamedKey::GroupPrevious => Named::GroupPrevious, +                NamedKey::ModeChange => Named::ModeChange, +                NamedKey::NextCandidate => Named::NextCandidate, +                NamedKey::NonConvert => Named::NonConvert, +                NamedKey::PreviousCandidate => Named::PreviousCandidate, +                NamedKey::Process => Named::Process, +                NamedKey::SingleCandidate => Named::SingleCandidate, +                NamedKey::HangulMode => Named::HangulMode, +                NamedKey::HanjaMode => Named::HanjaMode, +                NamedKey::JunjaMode => Named::JunjaMode, +                NamedKey::Eisu => Named::Eisu, +                NamedKey::Hankaku => Named::Hankaku, +                NamedKey::Hiragana => Named::Hiragana, +                NamedKey::HiraganaKatakana => Named::HiraganaKatakana, +                NamedKey::KanaMode => Named::KanaMode, +                NamedKey::KanjiMode => Named::KanjiMode, +                NamedKey::Katakana => Named::Katakana, +                NamedKey::Romaji => Named::Romaji, +                NamedKey::Zenkaku => Named::Zenkaku, +                NamedKey::ZenkakuHankaku => Named::ZenkakuHankaku, +                NamedKey::Soft1 => Named::Soft1, +                NamedKey::Soft2 => Named::Soft2, +                NamedKey::Soft3 => Named::Soft3, +                NamedKey::Soft4 => Named::Soft4, +                NamedKey::ChannelDown => Named::ChannelDown, +                NamedKey::ChannelUp => Named::ChannelUp, +                NamedKey::Close => Named::Close, +                NamedKey::MailForward => Named::MailForward, +                NamedKey::MailReply => Named::MailReply, +                NamedKey::MailSend => Named::MailSend, +                NamedKey::MediaClose => Named::MediaClose, +                NamedKey::MediaFastForward => Named::MediaFastForward, +                NamedKey::MediaPause => Named::MediaPause, +                NamedKey::MediaPlay => Named::MediaPlay, +                NamedKey::MediaPlayPause => Named::MediaPlayPause, +                NamedKey::MediaRecord => Named::MediaRecord, +                NamedKey::MediaRewind => Named::MediaRewind, +                NamedKey::MediaStop => Named::MediaStop, +                NamedKey::MediaTrackNext => Named::MediaTrackNext, +                NamedKey::MediaTrackPrevious => Named::MediaTrackPrevious, +                NamedKey::New => Named::New, +                NamedKey::Open => Named::Open, +                NamedKey::Print => Named::Print, +                NamedKey::Save => Named::Save, +                NamedKey::SpellCheck => Named::SpellCheck, +                NamedKey::Key11 => Named::Key11, +                NamedKey::Key12 => Named::Key12, +                NamedKey::AudioBalanceLeft => Named::AudioBalanceLeft, +                NamedKey::AudioBalanceRight => Named::AudioBalanceRight, +                NamedKey::AudioBassBoostDown => Named::AudioBassBoostDown, +                NamedKey::AudioBassBoostToggle => Named::AudioBassBoostToggle, +                NamedKey::AudioBassBoostUp => Named::AudioBassBoostUp, +                NamedKey::AudioFaderFront => Named::AudioFaderFront, +                NamedKey::AudioFaderRear => Named::AudioFaderRear, +                NamedKey::AudioSurroundModeNext => Named::AudioSurroundModeNext, +                NamedKey::AudioTrebleDown => Named::AudioTrebleDown, +                NamedKey::AudioTrebleUp => Named::AudioTrebleUp, +                NamedKey::AudioVolumeDown => Named::AudioVolumeDown, +                NamedKey::AudioVolumeUp => Named::AudioVolumeUp, +                NamedKey::AudioVolumeMute => Named::AudioVolumeMute, +                NamedKey::MicrophoneToggle => Named::MicrophoneToggle, +                NamedKey::MicrophoneVolumeDown => Named::MicrophoneVolumeDown, +                NamedKey::MicrophoneVolumeUp => Named::MicrophoneVolumeUp, +                NamedKey::MicrophoneVolumeMute => Named::MicrophoneVolumeMute, +                NamedKey::SpeechCorrectionList => Named::SpeechCorrectionList, +                NamedKey::SpeechInputToggle => Named::SpeechInputToggle, +                NamedKey::LaunchApplication1 => Named::LaunchApplication1, +                NamedKey::LaunchApplication2 => Named::LaunchApplication2, +                NamedKey::LaunchCalendar => Named::LaunchCalendar, +                NamedKey::LaunchContacts => Named::LaunchContacts, +                NamedKey::LaunchMail => Named::LaunchMail, +                NamedKey::LaunchMediaPlayer => Named::LaunchMediaPlayer, +                NamedKey::LaunchMusicPlayer => Named::LaunchMusicPlayer, +                NamedKey::LaunchPhone => Named::LaunchPhone, +                NamedKey::LaunchScreenSaver => Named::LaunchScreenSaver, +                NamedKey::LaunchSpreadsheet => Named::LaunchSpreadsheet, +                NamedKey::LaunchWebBrowser => Named::LaunchWebBrowser, +                NamedKey::LaunchWebCam => Named::LaunchWebCam, +                NamedKey::LaunchWordProcessor => Named::LaunchWordProcessor, +                NamedKey::BrowserBack => Named::BrowserBack, +                NamedKey::BrowserFavorites => Named::BrowserFavorites, +                NamedKey::BrowserForward => Named::BrowserForward, +                NamedKey::BrowserHome => Named::BrowserHome, +                NamedKey::BrowserRefresh => Named::BrowserRefresh, +                NamedKey::BrowserSearch => Named::BrowserSearch, +                NamedKey::BrowserStop => Named::BrowserStop, +                NamedKey::AppSwitch => Named::AppSwitch, +                NamedKey::Call => Named::Call, +                NamedKey::Camera => Named::Camera, +                NamedKey::CameraFocus => Named::CameraFocus, +                NamedKey::EndCall => Named::EndCall, +                NamedKey::GoBack => Named::GoBack, +                NamedKey::GoHome => Named::GoHome, +                NamedKey::HeadsetHook => Named::HeadsetHook, +                NamedKey::LastNumberRedial => Named::LastNumberRedial, +                NamedKey::Notification => Named::Notification, +                NamedKey::MannerMode => Named::MannerMode, +                NamedKey::VoiceDial => Named::VoiceDial, +                NamedKey::TV => Named::TV, +                NamedKey::TV3DMode => Named::TV3DMode, +                NamedKey::TVAntennaCable => Named::TVAntennaCable, +                NamedKey::TVAudioDescription => Named::TVAudioDescription, +                NamedKey::TVAudioDescriptionMixDown => { +                    Named::TVAudioDescriptionMixDown +                } +                NamedKey::TVAudioDescriptionMixUp => { +                    Named::TVAudioDescriptionMixUp +                } +                NamedKey::TVContentsMenu => Named::TVContentsMenu, +                NamedKey::TVDataService => Named::TVDataService, +                NamedKey::TVInput => Named::TVInput, +                NamedKey::TVInputComponent1 => Named::TVInputComponent1, +                NamedKey::TVInputComponent2 => Named::TVInputComponent2, +                NamedKey::TVInputComposite1 => Named::TVInputComposite1, +                NamedKey::TVInputComposite2 => Named::TVInputComposite2, +                NamedKey::TVInputHDMI1 => Named::TVInputHDMI1, +                NamedKey::TVInputHDMI2 => Named::TVInputHDMI2, +                NamedKey::TVInputHDMI3 => Named::TVInputHDMI3, +                NamedKey::TVInputHDMI4 => Named::TVInputHDMI4, +                NamedKey::TVInputVGA1 => Named::TVInputVGA1, +                NamedKey::TVMediaContext => Named::TVMediaContext, +                NamedKey::TVNetwork => Named::TVNetwork, +                NamedKey::TVNumberEntry => Named::TVNumberEntry, +                NamedKey::TVPower => Named::TVPower, +                NamedKey::TVRadioService => Named::TVRadioService, +                NamedKey::TVSatellite => Named::TVSatellite, +                NamedKey::TVSatelliteBS => Named::TVSatelliteBS, +                NamedKey::TVSatelliteCS => Named::TVSatelliteCS, +                NamedKey::TVSatelliteToggle => Named::TVSatelliteToggle, +                NamedKey::TVTerrestrialAnalog => Named::TVTerrestrialAnalog, +                NamedKey::TVTerrestrialDigital => Named::TVTerrestrialDigital, +                NamedKey::TVTimer => Named::TVTimer, +                NamedKey::AVRInput => Named::AVRInput, +                NamedKey::AVRPower => Named::AVRPower, +                NamedKey::ColorF0Red => Named::ColorF0Red, +                NamedKey::ColorF1Green => Named::ColorF1Green, +                NamedKey::ColorF2Yellow => Named::ColorF2Yellow, +                NamedKey::ColorF3Blue => Named::ColorF3Blue, +                NamedKey::ColorF4Grey => Named::ColorF4Grey, +                NamedKey::ColorF5Brown => Named::ColorF5Brown, +                NamedKey::ClosedCaptionToggle => Named::ClosedCaptionToggle, +                NamedKey::Dimmer => Named::Dimmer, +                NamedKey::DisplaySwap => Named::DisplaySwap, +                NamedKey::DVR => Named::DVR, +                NamedKey::Exit => Named::Exit, +                NamedKey::FavoriteClear0 => Named::FavoriteClear0, +                NamedKey::FavoriteClear1 => Named::FavoriteClear1, +                NamedKey::FavoriteClear2 => Named::FavoriteClear2, +                NamedKey::FavoriteClear3 => Named::FavoriteClear3, +                NamedKey::FavoriteRecall0 => Named::FavoriteRecall0, +                NamedKey::FavoriteRecall1 => Named::FavoriteRecall1, +                NamedKey::FavoriteRecall2 => Named::FavoriteRecall2, +                NamedKey::FavoriteRecall3 => Named::FavoriteRecall3, +                NamedKey::FavoriteStore0 => Named::FavoriteStore0, +                NamedKey::FavoriteStore1 => Named::FavoriteStore1, +                NamedKey::FavoriteStore2 => Named::FavoriteStore2, +                NamedKey::FavoriteStore3 => Named::FavoriteStore3, +                NamedKey::Guide => Named::Guide, +                NamedKey::GuideNextDay => Named::GuideNextDay, +                NamedKey::GuidePreviousDay => Named::GuidePreviousDay, +                NamedKey::Info => Named::Info, +                NamedKey::InstantReplay => Named::InstantReplay, +                NamedKey::Link => Named::Link, +                NamedKey::ListProgram => Named::ListProgram, +                NamedKey::LiveContent => Named::LiveContent, +                NamedKey::Lock => Named::Lock, +                NamedKey::MediaApps => Named::MediaApps, +                NamedKey::MediaAudioTrack => Named::MediaAudioTrack, +                NamedKey::MediaLast => Named::MediaLast, +                NamedKey::MediaSkipBackward => Named::MediaSkipBackward, +                NamedKey::MediaSkipForward => Named::MediaSkipForward, +                NamedKey::MediaStepBackward => Named::MediaStepBackward, +                NamedKey::MediaStepForward => Named::MediaStepForward, +                NamedKey::MediaTopMenu => Named::MediaTopMenu, +                NamedKey::NavigateIn => Named::NavigateIn, +                NamedKey::NavigateNext => Named::NavigateNext, +                NamedKey::NavigateOut => Named::NavigateOut, +                NamedKey::NavigatePrevious => Named::NavigatePrevious, +                NamedKey::NextFavoriteChannel => Named::NextFavoriteChannel, +                NamedKey::NextUserProfile => Named::NextUserProfile, +                NamedKey::OnDemand => Named::OnDemand, +                NamedKey::Pairing => Named::Pairing, +                NamedKey::PinPDown => Named::PinPDown, +                NamedKey::PinPMove => Named::PinPMove, +                NamedKey::PinPToggle => Named::PinPToggle, +                NamedKey::PinPUp => Named::PinPUp, +                NamedKey::PlaySpeedDown => Named::PlaySpeedDown, +                NamedKey::PlaySpeedReset => Named::PlaySpeedReset, +                NamedKey::PlaySpeedUp => Named::PlaySpeedUp, +                NamedKey::RandomToggle => Named::RandomToggle, +                NamedKey::RcLowBattery => Named::RcLowBattery, +                NamedKey::RecordSpeedNext => Named::RecordSpeedNext, +                NamedKey::RfBypass => Named::RfBypass, +                NamedKey::ScanChannelsToggle => Named::ScanChannelsToggle, +                NamedKey::ScreenModeNext => Named::ScreenModeNext, +                NamedKey::Settings => Named::Settings, +                NamedKey::SplitScreenToggle => Named::SplitScreenToggle, +                NamedKey::STBInput => Named::STBInput, +                NamedKey::STBPower => Named::STBPower, +                NamedKey::Subtitle => Named::Subtitle, +                NamedKey::Teletext => Named::Teletext, +                NamedKey::VideoModeNext => Named::VideoModeNext, +                NamedKey::Wink => Named::Wink, +                NamedKey::ZoomToggle => Named::ZoomToggle, +                NamedKey::F1 => Named::F1, +                NamedKey::F2 => Named::F2, +                NamedKey::F3 => Named::F3, +                NamedKey::F4 => Named::F4, +                NamedKey::F5 => Named::F5, +                NamedKey::F6 => Named::F6, +                NamedKey::F7 => Named::F7, +                NamedKey::F8 => Named::F8, +                NamedKey::F9 => Named::F9, +                NamedKey::F10 => Named::F10, +                NamedKey::F11 => Named::F11, +                NamedKey::F12 => Named::F12, +                NamedKey::F13 => Named::F13, +                NamedKey::F14 => Named::F14, +                NamedKey::F15 => Named::F15, +                NamedKey::F16 => Named::F16, +                NamedKey::F17 => Named::F17, +                NamedKey::F18 => Named::F18, +                NamedKey::F19 => Named::F19, +                NamedKey::F20 => Named::F20, +                NamedKey::F21 => Named::F21, +                NamedKey::F22 => Named::F22, +                NamedKey::F23 => Named::F23, +                NamedKey::F24 => Named::F24, +                NamedKey::F25 => Named::F25, +                NamedKey::F26 => Named::F26, +                NamedKey::F27 => Named::F27, +                NamedKey::F28 => Named::F28, +                NamedKey::F29 => Named::F29, +                NamedKey::F30 => Named::F30, +                NamedKey::F31 => Named::F31, +                NamedKey::F32 => Named::F32, +                NamedKey::F33 => Named::F33, +                NamedKey::F34 => Named::F34, +                NamedKey::F35 => Named::F35, +                _ => return keyboard::Key::Unidentified, +            })          } -        winit::event::VirtualKeyCode::NextTrack => KeyCode::NextTrack, -        winit::event::VirtualKeyCode::NoConvert => KeyCode::NoConvert, -        winit::event::VirtualKeyCode::NumpadComma => KeyCode::NumpadComma, -        winit::event::VirtualKeyCode::NumpadEnter => KeyCode::NumpadEnter, -        winit::event::VirtualKeyCode::NumpadEquals => KeyCode::NumpadEquals, -        winit::event::VirtualKeyCode::OEM102 => KeyCode::OEM102, -        winit::event::VirtualKeyCode::Period => KeyCode::Period, -        winit::event::VirtualKeyCode::PlayPause => KeyCode::PlayPause, -        winit::event::VirtualKeyCode::Power => KeyCode::Power, -        winit::event::VirtualKeyCode::PrevTrack => KeyCode::PrevTrack, -        winit::event::VirtualKeyCode::RAlt => KeyCode::RAlt, -        winit::event::VirtualKeyCode::RBracket => KeyCode::RBracket, -        winit::event::VirtualKeyCode::RControl => KeyCode::RControl, -        winit::event::VirtualKeyCode::RShift => KeyCode::RShift, -        winit::event::VirtualKeyCode::RWin => KeyCode::RWin, -        winit::event::VirtualKeyCode::Semicolon => KeyCode::Semicolon, -        winit::event::VirtualKeyCode::Slash => KeyCode::Slash, -        winit::event::VirtualKeyCode::Sleep => KeyCode::Sleep, -        winit::event::VirtualKeyCode::Stop => KeyCode::Stop, -        winit::event::VirtualKeyCode::NumpadSubtract => KeyCode::NumpadSubtract, -        winit::event::VirtualKeyCode::Sysrq => KeyCode::Sysrq, -        winit::event::VirtualKeyCode::Tab => KeyCode::Tab, -        winit::event::VirtualKeyCode::Underline => KeyCode::Underline, -        winit::event::VirtualKeyCode::Unlabeled => KeyCode::Unlabeled, -        winit::event::VirtualKeyCode::VolumeDown => KeyCode::VolumeDown, -        winit::event::VirtualKeyCode::VolumeUp => KeyCode::VolumeUp, -        winit::event::VirtualKeyCode::Wake => KeyCode::Wake, -        winit::event::VirtualKeyCode::WebBack => KeyCode::WebBack, -        winit::event::VirtualKeyCode::WebFavorites => KeyCode::WebFavorites, -        winit::event::VirtualKeyCode::WebForward => KeyCode::WebForward, -        winit::event::VirtualKeyCode::WebHome => KeyCode::WebHome, -        winit::event::VirtualKeyCode::WebRefresh => KeyCode::WebRefresh, -        winit::event::VirtualKeyCode::WebSearch => KeyCode::WebSearch, -        winit::event::VirtualKeyCode::WebStop => KeyCode::WebStop, -        winit::event::VirtualKeyCode::Yen => KeyCode::Yen, -        winit::event::VirtualKeyCode::Copy => KeyCode::Copy, -        winit::event::VirtualKeyCode::Paste => KeyCode::Paste, -        winit::event::VirtualKeyCode::Cut => KeyCode::Cut, -        winit::event::VirtualKeyCode::Asterisk => KeyCode::Asterisk, +        _ => keyboard::Key::Unidentified,      }  } @@ -655,13 +812,3 @@ pub fn icon(icon: window::Icon) -> Option<winit::window::Icon> {      winit::window::Icon::from_rgba(pixels, size.width, size.height).ok()  } - -// As defined in: http://www.unicode.org/faq/private_use.html -pub(crate) fn is_private_use_character(c: char) -> bool { -    matches!( -        c, -        '\u{E000}'..='\u{F8FF}' -        | '\u{F0000}'..='\u{FFFFD}' -        | '\u{100000}'..='\u{10FFFD}' -    ) -} diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index 84651d40..84c81bea 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -118,7 +118,10 @@ where      let mut debug = Debug::new();      debug.startup_started(); -    let event_loop = EventLoopBuilder::with_user_event().build(); +    let event_loop = EventLoopBuilder::with_user_event() +        .build() +        .expect("Create event loop"); +      let proxy = event_loop.create_proxy();      let runtime = { @@ -210,78 +213,78 @@ where      let mut context = task::Context::from_waker(task::noop_waker_ref()); -    platform::run(event_loop, move |event, window_target, control_flow| { -        use winit::event_loop::ControlFlow; - -        if let ControlFlow::ExitWithCode(_) = control_flow { +    let _ = event_loop.run(move |event, event_loop| { +        if event_loop.exiting() {              return;          } -        let event = match event { -            winit::event::Event::WindowEvent { -                event: -                    winit::event::WindowEvent::ScaleFactorChanged { -                        new_inner_size, -                        .. -                    }, -                window_id, -            } => Some(winit::event::Event::WindowEvent { -                event: winit::event::WindowEvent::Resized(*new_inner_size), -                window_id, -            }), -            _ => event.to_static(), -        }; - -        if let Some(event) = event { -            event_sender -                .start_send(Event::EventLoopAwakened(event)) -                .expect("Send event"); - -            loop { -                let poll = instance.as_mut().poll(&mut context); - -                match poll { -                    task::Poll::Pending => match control_receiver.try_next() { -                        Ok(Some(control)) => match control { -                            Control::ChangeFlow(flow) => { -                                *control_flow = flow; -                            } -                            Control::CreateWindow { -                                id, -                                settings, -                                title, -                                monitor, -                            } => { -                                let exit_on_close_request = -                                    settings.exit_on_close_request; - -                                let window = conversion::window_settings( -                                    settings, &title, monitor, None, -                                ) -                                .build(window_target) -                                .expect("Failed to build window"); - -                                event_sender -                                    .start_send(Event::WindowCreated { -                                        id, -                                        window, -                                        exit_on_close_request, -                                    }) -                                    .expect("Send event"); +        event_sender +            .start_send(Event::EventLoopAwakened(event)) +            .expect("Send event"); + +        loop { +            let poll = instance.as_mut().poll(&mut context); + +            match poll { +                task::Poll::Pending => match control_receiver.try_next() { +                    Ok(Some(control)) => match control { +                        Control::ChangeFlow(flow) => { +                            use winit::event_loop::ControlFlow; + +                            match (event_loop.control_flow(), flow) { +                                ( +                                    ControlFlow::WaitUntil(current), +                                    ControlFlow::WaitUntil(new), +                                ) if new < current => {} +                                ( +                                    ControlFlow::WaitUntil(target), +                                    ControlFlow::Wait, +                                ) if target > Instant::now() => {} +                                _ => { +                                    event_loop.set_control_flow(flow); +                                }                              } -                        }, -                        _ => { -                            break; +                        } +                        Control::CreateWindow { +                            id, +                            settings, +                            title, +                            monitor, +                        } => { +                            let exit_on_close_request = +                                settings.exit_on_close_request; + +                            let window = conversion::window_settings( +                                settings, &title, monitor, None, +                            ) +                            .build(event_loop) +                            .expect("Failed to build window"); + +                            event_sender +                                .start_send(Event::WindowCreated { +                                    id, +                                    window, +                                    exit_on_close_request, +                                }) +                                .expect("Send event"); +                        } +                        Control::Exit => { +                            event_loop.exit();                          }                      }, -                    task::Poll::Ready(_) => { -                        *control_flow = ControlFlow::Exit; +                    _ => {                          break;                      } -                }; -            } +                }, +                task::Poll::Ready(_) => { +                    event_loop.exit(); +                    break; +                } +            };          } -    }) +    }); + +    Ok(())  }  enum Event<Message: 'static> { @@ -290,11 +293,12 @@ enum Event<Message: 'static> {          window: winit::window::Window,          exit_on_close_request: bool,      }, -    EventLoopAwakened(winit::event::Event<'static, Message>), +    EventLoopAwakened(winit::event::Event<Message>),  }  enum Control {      ChangeFlow(winit::event_loop::ControlFlow), +    Exit,      CreateWindow {          id: window::Id,          settings: window::Settings, @@ -372,7 +376,6 @@ async fn run_instance<A, E, C>(      runtime.track(application.subscription().into_recipes());      let mut messages = Vec::new(); -    let mut redraw_pending = false;      debug.startup_finished(); @@ -419,191 +422,15 @@ async fn run_instance<A, E, C>(              }              Event::EventLoopAwakened(event) => {                  match event { -                    event::Event::NewEvents(start_cause) => { -                        redraw_pending = matches!( -                            start_cause, -                            event::StartCause::Init -                                | event::StartCause::Poll -                                | event::StartCause::ResumeTimeReached { .. } -                        ); -                    } -                    event::Event::MainEventsCleared => { -                        debug.event_processing_started(); -                        let mut uis_stale = false; - -                        for (id, window) in window_manager.iter_mut() { -                            let mut window_events = vec![]; - -                            events.retain(|(window_id, event)| { -                                if *window_id == Some(id) || window_id.is_none() -                                { -                                    window_events.push(event.clone()); -                                    false -                                } else { -                                    true -                                } -                            }); - -                            if !redraw_pending -                                && window_events.is_empty() -                                && messages.is_empty() -                            { -                                continue; -                            } - -                            let (ui_state, statuses) = user_interfaces -                                .get_mut(&id) -                                .expect("Get user interface") -                                .update( -                                    &window_events, -                                    window.state.cursor(), -                                    &mut window.renderer, -                                    &mut clipboard, -                                    &mut messages, -                                ); - -                            if !uis_stale { -                                uis_stale = matches!( -                                    ui_state, -                                    user_interface::State::Outdated -                                ); -                            } - -                            for (event, status) in window_events -                                .into_iter() -                                .zip(statuses.into_iter()) -                            { -                                runtime.broadcast(event, status); -                            } -                        } - -                        debug.event_processing_finished(); - -                        // TODO mw application update returns which window IDs to update -                        if !messages.is_empty() || uis_stale { -                            let mut cached_interfaces: HashMap< -                                window::Id, -                                user_interface::Cache, -                            > = ManuallyDrop::into_inner(user_interfaces) -                                .drain() -                                .map(|(id, ui)| (id, ui.into_cache())) -                                .collect(); - -                            // Update application -                            update( -                                &mut application, -                                &mut compositor, -                                &mut runtime, -                                &mut clipboard, -                                &mut control_sender, -                                &mut proxy, -                                &mut debug, -                                &mut messages, -                                &mut window_manager, -                                &mut cached_interfaces, -                            ); - -                            // we must synchronize all window states with application state after an -                            // application update since we don't know what changed -                            for (id, window) in window_manager.iter_mut() { -                                window.state.synchronize( -                                    &application, -                                    id, -                                    &window.raw, -                                ); -                            } - -                            // rebuild UIs with the synchronized states -                            user_interfaces = -                                ManuallyDrop::new(build_user_interfaces( -                                    &application, -                                    &mut debug, -                                    &mut window_manager, -                                    cached_interfaces, -                                )); -                        } - -                        debug.draw_started(); - -                        for (id, window) in window_manager.iter_mut() { -                            // TODO: Avoid redrawing all the time by forcing widgets to -                            //  request redraws on state changes -                            // -                            // Then, we can use the `interface_state` here to decide if a redraw -                            // is needed right away, or simply wait until a specific time. -                            let redraw_event = core::Event::Window( -                                id, -                                window::Event::RedrawRequested(Instant::now()), -                            ); - -                            let cursor = window.state.cursor(); - -                            let ui = user_interfaces -                                .get_mut(&id) -                                .expect("Get user interface"); - -                            let (ui_state, _) = ui.update( -                                &[redraw_event.clone()], -                                cursor, -                                &mut window.renderer, -                                &mut clipboard, -                                &mut messages, -                            ); - -                            let new_mouse_interaction = { -                                let state = &window.state; - -                                ui.draw( -                                    &mut window.renderer, -                                    state.theme(), -                                    &renderer::Style { -                                        text_color: state.text_color(), -                                    }, -                                    cursor, -                                ) -                            }; - -                            if new_mouse_interaction != window.mouse_interaction -                            { -                                window.raw.set_cursor_icon( -                                    conversion::mouse_interaction( -                                        new_mouse_interaction, -                                    ), -                                ); - -                                window.mouse_interaction = -                                    new_mouse_interaction; -                            } - +                    event::Event::NewEvents( +                        event::StartCause::Init +                        | event::StartCause::ResumeTimeReached { .. }, +                    ) => { +                        for (_id, window) in window_manager.iter_mut() {                              // TODO once widgets can request to be redrawn, we can avoid always requesting a                              // redraw                              window.raw.request_redraw(); - -                            runtime.broadcast( -                                redraw_event.clone(), -                                core::event::Status::Ignored, -                            ); - -                            let _ = control_sender.start_send( -                                Control::ChangeFlow(match ui_state { -                                    user_interface::State::Updated { -                                        redraw_request: Some(redraw_request), -                                    } => match redraw_request { -                                        window::RedrawRequest::NextFrame => { -                                            ControlFlow::Poll -                                        } -                                        window::RedrawRequest::At(at) => { -                                            ControlFlow::WaitUntil(at) -                                        } -                                    }, -                                    _ => ControlFlow::Wait, -                                }), -                            );                          } - -                        redraw_pending = false; - -                        debug.draw_finished();                      }                      event::Event::PlatformSpecific(                          event::PlatformSpecific::MacOS( @@ -624,13 +451,85 @@ async fn run_instance<A, E, C>(                      event::Event::UserEvent(message) => {                          messages.push(message);                      } -                    event::Event::RedrawRequested(id) => { +                    event::Event::WindowEvent { +                        window_id: id, +                        event: event::WindowEvent::RedrawRequested, +                        .. +                    } => {                          let Some((id, window)) =                              window_manager.get_mut_alias(id)                          else {                              continue;                          }; +                        // TODO: Avoid redrawing all the time by forcing widgets to +                        // request redraws on state changes +                        // +                        // Then, we can use the `interface_state` here to decide if a redraw +                        // is needed right away, or simply wait until a specific time. +                        let redraw_event = core::Event::Window( +                            id, +                            window::Event::RedrawRequested(Instant::now()), +                        ); + +                        let cursor = window.state.cursor(); + +                        let ui = user_interfaces +                            .get_mut(&id) +                            .expect("Get user interface"); + +                        let (ui_state, _) = ui.update( +                            &[redraw_event.clone()], +                            cursor, +                            &mut window.renderer, +                            &mut clipboard, +                            &mut messages, +                        ); + +                        debug.draw_started(); +                        let new_mouse_interaction = ui.draw( +                            &mut window.renderer, +                            window.state.theme(), +                            &renderer::Style { +                                text_color: window.state.text_color(), +                            }, +                            cursor, +                        ); +                        debug.draw_finished(); + +                        if new_mouse_interaction != window.mouse_interaction { +                            window.raw.set_cursor_icon( +                                conversion::mouse_interaction( +                                    new_mouse_interaction, +                                ), +                            ); + +                            window.mouse_interaction = new_mouse_interaction; +                        } + +                        runtime.broadcast( +                            redraw_event.clone(), +                            core::event::Status::Ignored, +                        ); + +                        let _ = control_sender.start_send(Control::ChangeFlow( +                            match ui_state { +                                user_interface::State::Updated { +                                    redraw_request: Some(redraw_request), +                                } => match redraw_request { +                                    window::RedrawRequest::NextFrame => { +                                        window.raw.request_redraw(); + +                                        ControlFlow::Wait +                                    } +                                    window::RedrawRequest::At(at) => { +                                        ControlFlow::WaitUntil(at) +                                    } +                                }, +                                _ => ControlFlow::Wait, +                            }, +                        )); +                          let physical_size = window.state.physical_size();                          if physical_size.width == 0 || physical_size.height == 0 @@ -638,14 +537,12 @@ async fn run_instance<A, E, C>(                              continue;                          } -                        debug.render_started();                          if window.viewport_version                              != window.state.viewport_version()                          {                              let logical_size = window.state.logical_size();                              debug.layout_started(); -                              let ui = user_interfaces                                  .remove(&id)                                  .expect("Remove user interface"); @@ -654,7 +551,6 @@ async fn run_instance<A, E, C>(                                  id,                                  ui.relayout(logical_size, &mut window.renderer),                              ); -                              debug.layout_finished();                              debug.draw_started(); @@ -669,6 +565,7 @@ async fn run_instance<A, E, C>(                                      },                                      window.state.cursor(),                                  ); +                            debug.draw_finished();                              if new_mouse_interaction != window.mouse_interaction                              { @@ -681,7 +578,6 @@ async fn run_instance<A, E, C>(                                  window.mouse_interaction =                                      new_mouse_interaction;                              } -                            debug.draw_finished();                              compositor.configure_surface(                                  &mut window.surface, @@ -693,6 +589,7 @@ async fn run_instance<A, E, C>(                                  window.state.viewport_version();                          } +                        debug.render_started();                          match compositor.present(                              &mut window.renderer,                              &mut window.surface, @@ -713,9 +610,11 @@ async fn run_instance<A, E, C>(                                  }                                  _ => {                                      debug.render_finished(); +                                      log::error!( -                                "Error {error:?} when presenting surface." -                            ); +                                        "Error {error:?} when \ +                                        presenting surface." +                                    );                                      // Try rendering all windows again next frame.                                      for (_id, window) in @@ -763,7 +662,7 @@ async fn run_instance<A, E, C>(                              if let Some(event) = conversion::window_event(                                  id, -                                &window_event, +                                window_event,                                  window.state.scale_factor(),                                  window.state.modifiers(),                              ) { @@ -771,6 +670,109 @@ async fn run_instance<A, E, C>(                              }                          }                      } +                    event::Event::AboutToWait => { +                        if events.is_empty() && messages.is_empty() { +                            continue; +                        } + +                        debug.event_processing_started(); +                        let mut uis_stale = false; + +                        for (id, window) in window_manager.iter_mut() { +                            let mut window_events = vec![]; + +                            events.retain(|(window_id, event)| { +                                if *window_id == Some(id) || window_id.is_none() +                                { +                                    window_events.push(event.clone()); +                                    false +                                } else { +                                    true +                                } +                            }); + +                            if window_events.is_empty() && messages.is_empty() { +                                continue; +                            } + +                            let (ui_state, statuses) = user_interfaces +                                .get_mut(&id) +                                .expect("Get user interface") +                                .update( +                                    &window_events, +                                    window.state.cursor(), +                                    &mut window.renderer, +                                    &mut clipboard, +                                    &mut messages, +                                ); + +                            window.raw.request_redraw(); + +                            if !uis_stale { +                                uis_stale = matches!( +                                    ui_state, +                                    user_interface::State::Outdated +                                ); +                            } + +                            for (event, status) in window_events +                                .into_iter() +                                .zip(statuses.into_iter()) +                            { +                                runtime.broadcast(event, status); +                            } +                        } + +                        debug.event_processing_finished(); + +                        // TODO mw application update returns which window IDs to update +                        if !messages.is_empty() || uis_stale { +                            let mut cached_interfaces: HashMap< +                                window::Id, +                                user_interface::Cache, +                            > = ManuallyDrop::into_inner(user_interfaces) +                                .drain() +                                .map(|(id, ui)| (id, ui.into_cache())) +                                .collect(); + +                            // Update application +                            update( +                                &mut application, +                                &mut compositor, +                                &mut runtime, +                                &mut clipboard, +                                &mut control_sender, +                                &mut proxy, +                                &mut debug, +                                &mut messages, +                                &mut window_manager, +                                &mut cached_interfaces, +                            ); + +                            // we must synchronize all window states with application state after an +                            // application update since we don't know what changed +                            for (id, window) in window_manager.iter_mut() { +                                window.state.synchronize( +                                    &application, +                                    id, +                                    &window.raw, +                                ); + +                                // TODO once widgets can request to be redrawn, we can avoid always requesting a +                                // redraw +                                window.raw.request_redraw(); +                            } + +                            // rebuild UIs with the synchronized states +                            user_interfaces = +                                ManuallyDrop::new(build_user_interfaces( +                                    &application, +                                    &mut debug, +                                    &mut window_manager, +                                    cached_interfaces, +                                )); +                        } +                    }                      _ => {}                  }              } @@ -901,16 +903,12 @@ fn run_command<A, C, E>(                          .expect("Send control action");                  }                  window::Action::Close(id) => { -                    use winit::event_loop::ControlFlow; -                      let _ = window_manager.remove(id);                      let _ = ui_caches.remove(&id);                      if window_manager.is_empty() {                          control_sender -                            .start_send(Control::ChangeFlow( -                                ControlFlow::ExitWithCode(0), -                            )) +                            .start_send(Control::Exit)                              .expect("Send control action");                      }                  } @@ -921,10 +919,12 @@ fn run_command<A, C, E>(                  }                  window::Action::Resize(id, size) => {                      if let Some(window) = window_manager.get_mut(id) { -                        window.raw.set_inner_size(winit::dpi::LogicalSize { -                            width: size.width, -                            height: size.height, -                        }); +                        let _ = window.raw.request_inner_size( +                            winit::dpi::LogicalSize { +                                width: size.width, +                                height: size.height, +                            }, +                        );                      }                  }                  window::Action::FetchSize(id, callback) => { @@ -942,11 +942,25 @@ fn run_command<A, C, E>(                              .expect("Send message to event loop");                      }                  } +                window::Action::FetchMaximized(id, callback) => { +                    if let Some(window) = window_manager.get_mut(id) { +                        proxy +                            .send_event(callback(window.raw.is_maximized())) +                            .expect("Send message to event loop"); +                    } +                }                  window::Action::Maximize(id, maximized) => {                      if let Some(window) = window_manager.get_mut(id) {                          window.raw.set_maximized(maximized);                      }                  } +                window::Action::FetchMinimized(id, callback) => { +                    if let Some(window) = window_manager.get_mut(id) { +                        proxy +                            .send_event(callback(window.raw.is_minimized())) +                            .expect("Send message to event loop"); +                    } +                }                  window::Action::Minimize(id, minimized) => {                      if let Some(window) = window_manager.get_mut(id) {                          window.raw.set_minimized(minimized); @@ -1153,60 +1167,20 @@ where  /// Returns true if the provided event should cause an [`Application`] to  /// exit.  pub fn user_force_quit( -    event: &winit::event::WindowEvent<'_>, -    _modifiers: winit::event::ModifiersState, +    event: &winit::event::WindowEvent, +    _modifiers: winit::keyboard::ModifiersState,  ) -> bool {      match event {          #[cfg(target_os = "macos")]          winit::event::WindowEvent::KeyboardInput { -            input: -                winit::event::KeyboardInput { -                    virtual_keycode: Some(winit::event::VirtualKeyCode::Q), +            event: +                winit::event::KeyEvent { +                    logical_key: winit::keyboard::Key::Character(c),                      state: winit::event::ElementState::Pressed,                      ..                  },              .. -        } if _modifiers.logo() => true, +        } if c == "q" && _modifiers.super_key() => true,          _ => false,      }  } - -#[cfg(not(target_arch = "wasm32"))] -mod platform { -    pub fn run<T, F>( -        mut event_loop: winit::event_loop::EventLoop<T>, -        event_handler: F, -    ) -> Result<(), super::Error> -    where -        F: 'static -            + FnMut( -                winit::event::Event<'_, T>, -                &winit::event_loop::EventLoopWindowTarget<T>, -                &mut winit::event_loop::ControlFlow, -            ), -    { -        use winit::platform::run_return::EventLoopExtRunReturn; - -        let _ = event_loop.run_return(event_handler); - -        Ok(()) -    } -} - -#[cfg(target_arch = "wasm32")] -mod platform { -    pub fn run<T, F>( -        event_loop: winit::event_loop::EventLoop<T>, -        event_handler: F, -    ) -> ! -    where -        F: 'static -            + FnMut( -                winit::event::Event<'_, T>, -                &winit::event_loop::EventLoopWindowTarget<T>, -                &mut winit::event_loop::ControlFlow, -            ), -    { -        event_loop.run(event_handler) -    } -} diff --git a/winit/src/multi_window/state.rs b/winit/src/multi_window/state.rs index 03da5ad7..235771f4 100644 --- a/winit/src/multi_window/state.rs +++ b/winit/src/multi_window/state.rs @@ -21,7 +21,7 @@ where      viewport: Viewport,      viewport_version: u64,      cursor_position: Option<winit::dpi::PhysicalPosition<f64>>, -    modifiers: winit::event::ModifiersState, +    modifiers: winit::keyboard::ModifiersState,      theme: <A::Renderer as core::Renderer>::Theme,      appearance: application::Appearance,  } @@ -72,7 +72,7 @@ where              viewport,              viewport_version: 0,              cursor_position: None, -            modifiers: winit::event::ModifiersState::default(), +            modifiers: winit::keyboard::ModifiersState::default(),              theme,              appearance,          } @@ -119,7 +119,7 @@ where      }      /// Returns the current keyboard modifiers of the [`State`]. -    pub fn modifiers(&self) -> winit::event::ModifiersState { +    pub fn modifiers(&self) -> winit::keyboard::ModifiersState {          self.modifiers      } @@ -142,7 +142,7 @@ where      pub fn update(          &mut self,          window: &Window, -        event: &WindowEvent<'_>, +        event: &WindowEvent,          _debug: &mut crate::runtime::Debug,      ) {          match event { @@ -158,10 +158,9 @@ where              }              WindowEvent::ScaleFactorChanged {                  scale_factor: new_scale_factor, -                new_inner_size, +                ..              } => { -                let size = -                    Size::new(new_inner_size.width, new_inner_size.height); +                let size = self.viewport.physical_size();                  self.viewport = Viewport::with_physical_size(                      size, @@ -180,13 +179,16 @@ where                  self.cursor_position = None;              }              WindowEvent::ModifiersChanged(new_modifiers) => { -                self.modifiers = *new_modifiers; +                self.modifiers = new_modifiers.state();              }              #[cfg(feature = "debug")]              WindowEvent::KeyboardInput { -                input: -                    winit::event::KeyboardInput { -                        virtual_keycode: Some(winit::event::VirtualKeyCode::F12), +                event: +                    winit::event::KeyEvent { +                        logical_key: +                            winit::keyboard::Key::Named( +                                winit::keyboard::NamedKey::F12, +                            ),                          state: winit::event::ElementState::Pressed,                          ..                      },  | 
