Skip to content

Commit

Permalink
Use custom opaque wrapper instead of the button-based "overlay" ele…
Browse files Browse the repository at this point in the history
…ment in the stack to block mouse events for elements under a dialog;

Implement esc_handler to handle Esc presses in dialogs;
Minor visual improvements;
  • Loading branch information
ImplOfAnImpl committed Jan 28, 2025
1 parent eff8579 commit 6881ba4
Show file tree
Hide file tree
Showing 5 changed files with 415 additions and 42 deletions.
110 changes: 69 additions & 41 deletions node-gui/src/main_window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ use common::{
primitives::{per_thousand::PerThousand, semver::SemVer, user_agent::UserAgent, Amount},
};
use iced::{
widget::{
button::{Button, Style},
center, text, Stack, Text,
},
widget::{center, container, Stack, Text},
Element, Length, Task,
};
use logging::log;
Expand All @@ -44,7 +41,9 @@ use crate::{
main_window::{main_menu::MenuMessage, main_widget::MainWidgetMessage},
widgets::{
confirm_broadcast::new_confirm_broadcast,
esc_handler::esc_handler,
new_wallet_account::new_wallet_account,
opaque::opaque,
popup_dialog::{popup_dialog, Popup},
wallet_mnemonic::{wallet_mnemonic_dialog, ImportState},
wallet_set_password::{wallet_set_password_dialog, SetPasswordState},
Expand Down Expand Up @@ -840,19 +839,6 @@ impl MainWindow {
iced::widget::Column::new().height(70),
];

let overlay = || {
Button::new(text(""))
.width(Length::Fill)
.height(Length::Fill)
.style(|_theme, _status| Style {
background: Some(iced::Background::Color(iced::Color::TRANSPARENT)),
text_color: iced::Color::BLACK,
..Style::default()
})
.on_press(MainWindowMessage::IgnoreEvent)
.into()
};

let show_dialog = self.active_dialog != ActiveDialog::None;
let dialog = show_dialog
.then(move || -> Element<MainWindowMessage> {
Expand Down Expand Up @@ -975,30 +961,72 @@ impl MainWindow {
}
}
})
.map(|d| [overlay(), center(d).into()]);

let popup_opt = self.popups.last();
let show_popup = popup_opt.is_some() || self.file_dialog_active;
let popup = show_popup
.then(move || -> Element<MainWindowMessage> {
match (self.file_dialog_active, popup_opt) {
(true, _) => Text::new("File dialog...").into(),
(_, Some(popup)) => {
popup_dialog(popup.clone(), MainWindowMessage::ClosePopup).into()
}
(_, None) => Text::new("Nothing to show").into(),
.map(|d| {
esc_handler(
centered_with_opaque_blurred_bg(d),
Some(MainWindowMessage::CloseDialog),
)
});

let popup: Option<Element<MainWindowMessage>> =
match (self.file_dialog_active, self.popups.last()) {
(true, _) => Some(bordered_text_on_normal_bg("File dialog...")),

(_, Some(popup)) => {
Some(popup_dialog(popup.clone(), MainWindowMessage::ClosePopup).into())
}
})
.map(|d| [overlay(), center(d).into()]);

Stack::with_children(
[main_content.into()]
.into_iter()
.chain(dialog.into_iter().flatten())
.chain(popup.into_iter().flatten()),
)
.width(Length::Fill)
.height(Length::Fill)
.into()
(_, None) => None,
};
let popup = popup.map(|d| {
esc_handler(
centered_with_opaque_blurred_bg(d),
(!self.file_dialog_active).then_some(MainWindowMessage::ClosePopup),
)
});

Stack::with_children(std::iter::once(main_content.into()).chain(dialog).chain(popup))
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}

fn bordered_text_on_normal_bg<'a, Message: 'a>(text: &'a str) -> Element<'a, Message> {
container(Text::new(text))
.style(|theme: &iced::Theme| {
let palette = theme.extended_palette();

container::Style {
background: Some(palette.background.base.color.into()),
border: iced::Border {
width: 1.0,
radius: 2.0.into(),
color: palette.background.strong.color,
},
..container::Style::default()
}
})
.padding([5, 10])
.into()
}

/// Create a container that
/// 1) fills all the available space and centers the provided content inside;
/// 2) is opaque to mouse events;
/// 3) uses semi-transparent white as the background, which creates the blurred background effect.
fn centered_with_opaque_blurred_bg<'a, Message: 'a>(
content: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
opaque(center(content).style(|_theme| {
container::Style {
background: Some(
iced::Color {
a: 0.5,
..iced::Color::WHITE
}
.into(),
),
..container::Style::default()
}
}))
}
172 changes: 172 additions & 0 deletions node-gui/src/widgets/esc_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright (c) 2025 RBB S.r.l
// [email protected]
// SPDX-License-Identifier: MIT
// Licensed under the MIT License;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use iced::{
advanced::{
layout, renderer,
widget::{tree, Operation, Tree},
Clipboard, Layout, Shell, Widget,
},
event, keyboard,
mouse::{self},
overlay, Element, Event, Length, Rectangle, Size, Vector,
};

/// Wrap an element, capturing Esc key press events and optionally emitting the specified message
/// on each key press.
///
/// Note: the Esc key press events will be consumed even if msg_to_emit is None.
pub fn esc_handler<'a, Message, Theme, Renderer>(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
msg_to_emit: Option<Message>,
) -> Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Theme: 'a,
Renderer: iced::advanced::Renderer + 'a,
{
Element::new(EscHandler {
content: content.into(),
msg_to_emit,
})
}

struct EscHandler<'a, Message, Theme, Renderer> {
content: Element<'a, Message, Theme, Renderer>,
msg_to_emit: Option<Message>,
}

impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for EscHandler<'_, Message, Theme, Renderer>
where
Renderer: iced::advanced::Renderer,
Message: Clone,
{
fn tag(&self) -> tree::Tag {
self.content.as_widget().tag()
}

fn state(&self) -> tree::State {
self.content.as_widget().state()
}

fn children(&self) -> Vec<Tree> {
self.content.as_widget().children()
}

fn diff(&self, tree: &mut Tree) {
self.content.as_widget().diff(tree);
}

fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}

fn size_hint(&self) -> Size<Length> {
self.content.as_widget().size_hint()
}

fn layout(
&self,
tree: &mut Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
self.content.as_widget().layout(tree, renderer, limits)
}

fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content
.as_widget()
.draw(tree, renderer, theme, style, layout, cursor, viewport);
}

fn operate(
&self,
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation,
) {
self.content.as_widget().operate(state, layout, renderer, operation);
}

fn on_event(
&mut self,
state: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
let event_captured = match &event {
Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => {
if *key == keyboard::Key::Named(keyboard::key::Named::Escape) {
if let Some(msg) = &self.msg_to_emit {
shell.publish(msg.clone());
}

true
} else {
false
}
}
_ => false,
};

if event_captured {
event::Status::Captured
} else {
self.content.as_widget_mut().on_event(
state, event, layout, cursor, renderer, clipboard, shell, viewport,
)
}
}

fn mouse_interaction(
&self,
state: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.content
.as_widget()
.mouse_interaction(state, layout, cursor, viewport, renderer)
}

fn overlay<'b>(
&'b mut self,
state: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(state, layout, renderer, translation)
}
}
2 changes: 2 additions & 0 deletions node-gui/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
// limitations under the License.

pub mod confirm_broadcast;
pub mod esc_handler;
pub mod new_wallet_account;
pub mod opaque;
pub mod popup_dialog;
pub mod wallet_mnemonic;
pub mod wallet_set_password;
Expand Down
Loading

0 comments on commit 6881ba4

Please sign in to comment.