Add ui component toast from iced repo examples
This commit is contained in:
parent
71bc585ed3
commit
a539cfe157
@ -31,7 +31,7 @@ pub enum Message {
|
||||
SpendTxs(Result<Vec<SpendTx>, Error>),
|
||||
Psbt(Result<Psbt, Error>),
|
||||
Recovery(Result<SpendTx, Error>),
|
||||
Signed(Result<(Psbt, Fingerprint), Error>),
|
||||
Signed(Fingerprint, Result<Psbt, Error>),
|
||||
WalletRegistered(Result<Fingerprint, Error>),
|
||||
Updated(Result<(), Error>),
|
||||
Saved(Result<(), Error>),
|
||||
|
||||
@ -10,6 +10,7 @@ use liana::{
|
||||
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network},
|
||||
};
|
||||
|
||||
use liana_ui::component::toast;
|
||||
use liana_ui::{
|
||||
component::{form, modal},
|
||||
widget::Element,
|
||||
@ -132,12 +133,26 @@ impl PsbtState {
|
||||
match &message {
|
||||
Message::View(view::Message::Spend(msg)) => match msg {
|
||||
view::SpendTxMessage::Cancel => {
|
||||
if let Some(PsbtAction::Sign(SignAction { display_modal, .. })) =
|
||||
&mut self.action
|
||||
{
|
||||
*display_modal = false;
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
self.action = None;
|
||||
}
|
||||
view::SpendTxMessage::Delete => {
|
||||
self.action = Some(PsbtAction::Delete(DeleteAction::default()));
|
||||
}
|
||||
view::SpendTxMessage::Sign => {
|
||||
if let Some(PsbtAction::Sign(SignAction { display_modal, .. })) =
|
||||
&mut self.action
|
||||
{
|
||||
*display_modal = true;
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
let action = SignAction::new(
|
||||
self.tx.signers(),
|
||||
self.wallet.clone(),
|
||||
@ -365,12 +380,12 @@ impl Action for DeleteAction {
|
||||
|
||||
pub struct SignAction {
|
||||
wallet: Arc<Wallet>,
|
||||
chosen_hw: Option<usize>,
|
||||
processing: bool,
|
||||
hws: HardwareWallets,
|
||||
error: Option<Error>,
|
||||
signing: HashSet<Fingerprint>,
|
||||
signed: HashSet<Fingerprint>,
|
||||
is_saved: bool,
|
||||
display_modal: bool,
|
||||
}
|
||||
|
||||
impl SignAction {
|
||||
@ -382,13 +397,13 @@ impl SignAction {
|
||||
is_saved: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
chosen_hw: None,
|
||||
processing: false,
|
||||
signing: HashSet::new(),
|
||||
hws: HardwareWallets::new(datadir_path, network).with_wallet(wallet.clone()),
|
||||
wallet,
|
||||
error: None,
|
||||
signed,
|
||||
is_saved,
|
||||
display_modal: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -416,61 +431,61 @@ impl Action for SignAction {
|
||||
..
|
||||
}) = self.hws.list.get(i)
|
||||
{
|
||||
self.chosen_hw = Some(i);
|
||||
self.processing = true;
|
||||
self.display_modal = false;
|
||||
self.signing.insert(*fingerprint);
|
||||
let psbt = tx.psbt.clone();
|
||||
let fingerprint = *fingerprint;
|
||||
return Command::perform(
|
||||
sign_psbt(self.wallet.clone(), device.clone(), *fingerprint, psbt),
|
||||
Message::Signed,
|
||||
sign_psbt(self.wallet.clone(), device.clone(), psbt),
|
||||
move |res| Message::Signed(fingerprint, res),
|
||||
);
|
||||
}
|
||||
}
|
||||
Message::View(view::Message::Spend(view::SpendTxMessage::SelectHotSigner)) => {
|
||||
self.processing = true;
|
||||
return Command::perform(
|
||||
sign_psbt_with_hot_signer(self.wallet.clone(), tx.psbt.clone()),
|
||||
Message::Signed,
|
||||
|(fg, res)| Message::Signed(fg, res),
|
||||
);
|
||||
}
|
||||
Message::Signed(res) => match res {
|
||||
Err(e) => self.error = Some(e),
|
||||
Ok((psbt, fingerprint)) => {
|
||||
self.error = None;
|
||||
self.signed.insert(fingerprint);
|
||||
let daemon = daemon.clone();
|
||||
tx.psbt = psbt.clone();
|
||||
if self.is_saved {
|
||||
return Command::perform(
|
||||
async move { daemon.update_spend_tx(&psbt).map_err(|e| e.into()) },
|
||||
Message::Updated,
|
||||
);
|
||||
// If the spend transaction was never saved before, then both the psbt and
|
||||
// labels attached to it must be updated.
|
||||
} else {
|
||||
let mut labels = HashMap::<LabelItem, Option<String>>::new();
|
||||
for (item, label) in tx.labels() {
|
||||
if !label.is_empty() {
|
||||
labels.insert(label_item_from_str(item), Some(label.clone()));
|
||||
Message::Signed(fingerprint, res) => {
|
||||
self.signing.remove(&fingerprint);
|
||||
match res {
|
||||
Err(e) => self.error = Some(e),
|
||||
Ok(psbt) => {
|
||||
self.error = None;
|
||||
self.signed.insert(fingerprint);
|
||||
let daemon = daemon.clone();
|
||||
tx.psbt = psbt.clone();
|
||||
if self.is_saved {
|
||||
return Command::perform(
|
||||
async move { daemon.update_spend_tx(&psbt).map_err(|e| e.into()) },
|
||||
Message::Updated,
|
||||
);
|
||||
// If the spend transaction was never saved before, then both the psbt and
|
||||
// labels attached to it must be updated.
|
||||
} else {
|
||||
let mut labels = HashMap::<LabelItem, Option<String>>::new();
|
||||
for (item, label) in tx.labels() {
|
||||
if !label.is_empty() {
|
||||
labels.insert(label_item_from_str(item), Some(label.clone()));
|
||||
}
|
||||
}
|
||||
return Command::perform(
|
||||
async move {
|
||||
daemon.update_spend_tx(&psbt)?;
|
||||
daemon.update_labels(&labels).map_err(|e| e.into())
|
||||
},
|
||||
Message::Updated,
|
||||
);
|
||||
}
|
||||
return Command::perform(
|
||||
async move {
|
||||
daemon.update_spend_tx(&psbt)?;
|
||||
daemon.update_labels(&labels).map_err(|e| e.into())
|
||||
},
|
||||
Message::Updated,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Message::Updated(res) => match res {
|
||||
Ok(()) => {
|
||||
self.processing = false;
|
||||
match self.wallet.main_descriptor.partial_spend_info(&tx.psbt) {
|
||||
Ok(sigs) => tx.sigs = sigs,
|
||||
Err(e) => self.error = Some(Error::Unexpected(e.to_string())),
|
||||
}
|
||||
}
|
||||
Ok(()) => match self.wallet.main_descriptor.partial_spend_info(&tx.psbt) {
|
||||
Ok(sigs) => tx.sigs = sigs,
|
||||
Err(e) => self.error = Some(Error::Unexpected(e.to_string())),
|
||||
},
|
||||
Err(e) => self.error = Some(e),
|
||||
},
|
||||
|
||||
@ -482,56 +497,62 @@ impl Action for SignAction {
|
||||
self.error = Some(e.into());
|
||||
}
|
||||
},
|
||||
Message::View(view::Message::Reload) => {
|
||||
self.chosen_hw = None;
|
||||
self.error = None;
|
||||
return self.load(daemon);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> {
|
||||
modal::Modal::new(
|
||||
let content = toast::Manager::new(
|
||||
content,
|
||||
view::psbt::sign_action(
|
||||
self.error.as_ref(),
|
||||
&self.hws.list,
|
||||
self.wallet.signer.as_ref().map(|s| s.fingerprint()),
|
||||
self.wallet
|
||||
.signer
|
||||
.as_ref()
|
||||
.and_then(|signer| self.wallet.keys_aliases.get(&signer.fingerprint)),
|
||||
self.processing,
|
||||
self.chosen_hw,
|
||||
&self.signed,
|
||||
),
|
||||
view::psbt::sign_action_toasts(&self.hws.list, &self.signing),
|
||||
)
|
||||
.on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel)))
|
||||
.into()
|
||||
.into();
|
||||
if self.display_modal {
|
||||
modal::Modal::new(
|
||||
content,
|
||||
view::psbt::sign_action(
|
||||
self.error.as_ref(),
|
||||
&self.hws.list,
|
||||
self.wallet.signer.as_ref().map(|s| s.fingerprint()),
|
||||
self.wallet
|
||||
.signer
|
||||
.as_ref()
|
||||
.and_then(|signer| self.wallet.keys_aliases.get(&signer.fingerprint)),
|
||||
&self.signed,
|
||||
&self.signing,
|
||||
),
|
||||
)
|
||||
.on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel)))
|
||||
.into()
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn sign_psbt_with_hot_signer(
|
||||
wallet: Arc<Wallet>,
|
||||
psbt: Psbt,
|
||||
) -> Result<(Psbt, Fingerprint), Error> {
|
||||
) -> (Fingerprint, Result<Psbt, Error>) {
|
||||
if let Some(signer) = &wallet.signer {
|
||||
let psbt = signer.sign_psbt(psbt).map_err(|e| {
|
||||
WalletError::HotSigner(format!("Hot signer failed to sign psbt: {}", e))
|
||||
})?;
|
||||
Ok((psbt, signer.fingerprint()))
|
||||
let res = signer
|
||||
.sign_psbt(psbt)
|
||||
.map_err(|e| WalletError::HotSigner(format!("Hot signer failed to sign psbt: {}", e)))
|
||||
.map_err(|e| e.into());
|
||||
(signer.fingerprint(), res)
|
||||
} else {
|
||||
Err(WalletError::HotSigner("Hot signer not loaded".to_string()).into())
|
||||
(
|
||||
Fingerprint::default(),
|
||||
Err(WalletError::HotSigner("Hot signer not loaded".to_string()).into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn sign_psbt(
|
||||
wallet: Arc<Wallet>,
|
||||
hw: std::sync::Arc<dyn async_hwi::HWI + Send + Sync>,
|
||||
fingerprint: Fingerprint,
|
||||
mut psbt: Psbt,
|
||||
) -> Result<(Psbt, Fingerprint), Error> {
|
||||
) -> Result<Psbt, Error> {
|
||||
// The BitBox02 is only going to produce a signature for a single key in the Script. In order
|
||||
// to make sure it doesn't sign for a public key from another spending path we remove the BIP32
|
||||
// derivation for the other paths.
|
||||
@ -560,7 +581,7 @@ async fn sign_psbt(
|
||||
} else {
|
||||
hw.sign_tx(&mut psbt).await.map_err(Error::from)?;
|
||||
}
|
||||
Ok((psbt, fingerprint))
|
||||
Ok(psbt)
|
||||
}
|
||||
|
||||
pub struct UpdateAction {
|
||||
|
||||
@ -11,9 +11,8 @@ use async_hwi::DeviceKind;
|
||||
pub fn hw_list_view(
|
||||
i: usize,
|
||||
hw: &HardwareWallet,
|
||||
chosen: bool,
|
||||
processing: bool,
|
||||
signed: bool,
|
||||
signing: bool,
|
||||
) -> Element<Message> {
|
||||
let mut bttn = Button::new(match hw {
|
||||
HardwareWallet::Supported {
|
||||
@ -24,7 +23,7 @@ pub fn hw_list_view(
|
||||
registered,
|
||||
..
|
||||
} => {
|
||||
if chosen && processing {
|
||||
if signing {
|
||||
hw::processing_hardware_wallet(kind, version.as_ref(), fingerprint, alias.as_ref())
|
||||
} else if signed {
|
||||
hw::sign_success_hardware_wallet(
|
||||
@ -61,7 +60,7 @@ pub fn hw_list_view(
|
||||
})
|
||||
.style(theme::Button::Border)
|
||||
.width(Length::Fill);
|
||||
if !processing {
|
||||
if !signing {
|
||||
if let HardwareWallet::Supported { registered, .. } = hw {
|
||||
if *registered != Some(false) {
|
||||
bttn = bttn.on_press(Message::SelectHardwareWallet(i));
|
||||
|
||||
@ -941,9 +941,8 @@ pub fn sign_action<'a>(
|
||||
hws: &'a [HardwareWallet],
|
||||
signer: Option<Fingerprint>,
|
||||
signer_alias: Option<&'a String>,
|
||||
processing: bool,
|
||||
chosen_hw: Option<usize>,
|
||||
signed: &HashSet<Fingerprint>,
|
||||
signing: &HashSet<Fingerprint>,
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push_maybe(warning.map(|w| warn(Some(w))))
|
||||
@ -963,11 +962,12 @@ pub fn sign_action<'a>(
|
||||
col.push(hw_list_view(
|
||||
i,
|
||||
hw,
|
||||
Some(i) == chosen_hw,
|
||||
processing,
|
||||
hw.fingerprint()
|
||||
.map(|f| signed.contains(&f))
|
||||
.unwrap_or(false),
|
||||
hw.fingerprint()
|
||||
.map(|f| signing.contains(&f))
|
||||
.unwrap_or(false),
|
||||
))
|
||||
},
|
||||
))
|
||||
@ -992,6 +992,41 @@ pub fn sign_action<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn sign_action_toasts<'a>(
|
||||
hws: &'a [HardwareWallet],
|
||||
signing: &HashSet<Fingerprint>,
|
||||
) -> Vec<Element<'a, Message>> {
|
||||
hws.iter()
|
||||
.filter_map(|hw| {
|
||||
if let HardwareWallet::Supported {
|
||||
kind,
|
||||
fingerprint,
|
||||
version,
|
||||
alias,
|
||||
..
|
||||
} = &hw
|
||||
{
|
||||
if signing.contains(fingerprint) {
|
||||
Some(
|
||||
card::simple(liana_ui::component::hw::processing_hardware_wallet(
|
||||
kind,
|
||||
version.as_ref(),
|
||||
fingerprint,
|
||||
alias.as_ref(),
|
||||
))
|
||||
.width(Length::Fixed(500.0))
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn update_spend_view<'a>(
|
||||
psbt: String,
|
||||
updated: &form::Value<String>,
|
||||
|
||||
@ -9,6 +9,7 @@ pub mod hw;
|
||||
pub mod modal;
|
||||
pub mod notification;
|
||||
pub mod text;
|
||||
pub mod toast;
|
||||
pub mod tooltip;
|
||||
|
||||
pub use tooltip::tooltip;
|
||||
|
||||
352
gui/ui/src/component/toast.rs
Normal file
352
gui/ui/src/component/toast.rs
Normal file
@ -0,0 +1,352 @@
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::card;
|
||||
use super::text::h1;
|
||||
use super::theme::Theme;
|
||||
use crate::widget::*;
|
||||
|
||||
use iced::{Alignment, Element, Length, Point, Rectangle, Size, Vector};
|
||||
use iced_native::widget::{Operation, Tree};
|
||||
use iced_native::{event, layout, mouse, overlay, renderer, window};
|
||||
use iced_native::{Clipboard, Event, Layout, Shell, Widget};
|
||||
|
||||
pub trait Toast {
|
||||
fn title(&self) -> &str;
|
||||
fn body(&self) -> &str;
|
||||
}
|
||||
|
||||
pub struct Manager<'a, Message, Renderer> {
|
||||
content: Element<'a, Message, Renderer>,
|
||||
toasts: Vec<Element<'a, Message, Renderer>>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Manager<'a, Message, iced::Renderer<Theme>>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
{
|
||||
pub fn new(
|
||||
content: impl Into<Element<'a, Message, iced::Renderer<Theme>>>,
|
||||
toasts: Vec<Element<'a, Message, iced::Renderer<Theme>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
content: content.into(),
|
||||
toasts: toasts.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Manager<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.content.as_widget().height()
|
||||
}
|
||||
|
||||
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn tag(&self) -> iced_native::widget::tree::Tag {
|
||||
struct Marker(Vec<Instant>);
|
||||
iced_native::widget::tree::Tag::of::<Marker>()
|
||||
}
|
||||
|
||||
fn state(&self) -> iced_native::widget::tree::State {
|
||||
iced_native::widget::tree::State::new(Vec::<Option<Instant>>::new())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
std::iter::once(Tree::new(&self.content))
|
||||
.chain(self.toasts.iter().map(Tree::new))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
let instants = tree.state.downcast_mut::<Vec<Option<Instant>>>();
|
||||
|
||||
// Invalidating removed instants to None allows us to remove
|
||||
// them here so that diffing for removed / new toast instants
|
||||
// is accurate
|
||||
instants.retain(Option::is_some);
|
||||
|
||||
match (instants.len(), self.toasts.len()) {
|
||||
(old, new) if old > new => {
|
||||
instants.truncate(new);
|
||||
}
|
||||
(old, new) if old < new => {
|
||||
instants.extend(std::iter::repeat(Some(Instant::now())).take(new - old));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tree.diff_children(
|
||||
&std::iter::once(&self.content)
|
||||
.chain(self.toasts.iter())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.content
|
||||
.as_widget()
|
||||
.operate(&mut state.children[0], layout, renderer, operation);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
state: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut state.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&state.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&state.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let instants = state.state.downcast_mut::<Vec<Option<Instant>>>();
|
||||
|
||||
let (content_state, toasts_state) = state.children.split_at_mut(1);
|
||||
|
||||
let content = self
|
||||
.content
|
||||
.as_widget_mut()
|
||||
.overlay(&mut content_state[0], layout, renderer);
|
||||
|
||||
let toasts = (!self.toasts.is_empty()).then(|| {
|
||||
overlay::Element::new(
|
||||
layout.bounds().position(),
|
||||
Box::new(Overlay {
|
||||
toasts: &mut self.toasts,
|
||||
state: toasts_state,
|
||||
instants,
|
||||
}),
|
||||
)
|
||||
});
|
||||
let overlays = content.into_iter().chain(toasts).collect::<Vec<_>>();
|
||||
|
||||
(!overlays.is_empty()).then(|| overlay::Group::with_children(overlays).overlay())
|
||||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Renderer> {
|
||||
toasts: &'b mut [Element<'a, Message, Renderer>],
|
||||
state: &'b mut [Tree],
|
||||
instants: &'b mut [Option<Instant>],
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
|
||||
for Overlay<'a, 'b, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
{
|
||||
fn layout(&self, renderer: &Renderer, bounds: Size, position: Point) -> layout::Node {
|
||||
let limits = layout::Limits::new(Size::ZERO, bounds)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
layout::flex::resolve(
|
||||
layout::flex::Axis::Vertical,
|
||||
renderer,
|
||||
&limits,
|
||||
10.into(),
|
||||
10.0,
|
||||
Alignment::End,
|
||||
self.toasts,
|
||||
)
|
||||
.translate(Vector::new(position.x, position.y))
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.toasts
|
||||
.iter_mut()
|
||||
.zip(self.state.iter_mut())
|
||||
.zip(layout.children())
|
||||
.zip(self.instants.iter_mut())
|
||||
.map(|(((child, state), layout), instant)| {
|
||||
let mut local_messages = vec![];
|
||||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
let status = child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
&mut local_shell,
|
||||
);
|
||||
|
||||
if !local_shell.is_empty() {
|
||||
instant.take();
|
||||
}
|
||||
|
||||
shell.merge(local_shell, std::convert::identity);
|
||||
|
||||
status
|
||||
})
|
||||
.fold(event::Status::Ignored, event::Status::merge)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &<Renderer as iced_native::Renderer>::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) {
|
||||
let viewport = layout.bounds();
|
||||
|
||||
for ((child, state), layout) in self
|
||||
.toasts
|
||||
.iter()
|
||||
.zip(self.state.iter())
|
||||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget().draw(
|
||||
state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
&viewport,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_native::widget::Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.toasts
|
||||
.iter()
|
||||
.zip(self.state.iter_mut())
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), layout)| {
|
||||
child
|
||||
.as_widget()
|
||||
.operate(state, layout, renderer, operation);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.toasts
|
||||
.iter()
|
||||
.zip(self.state.iter())
|
||||
.zip(layout.children())
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget().mouse_interaction(
|
||||
state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool {
|
||||
layout
|
||||
.children()
|
||||
.any(|layout| layout.bounds().contains(cursor_position))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Manager<'a, Message, Renderer>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + iced_native::Renderer,
|
||||
Message: 'a,
|
||||
{
|
||||
fn from(manager: Manager<'a, Message, Renderer>) -> Self {
|
||||
Element::new(manager)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user