Merge #275: gui: add modal widget and change spend actions
bde519fa7bf1eeed72950961d09eaf96acfc9e93 Integrate modal component in spend tx actions (edouard) 7ffde7381b166acabd121e1915365cc8b517d566 ui: add modal component (edouard) Pull request description: based on #274 close #198 close #171 close #235 ACKs for top commit: edouardparis: Self-ACK bde519fa7bf1eeed72950961d09eaf96acfc9e93 Tree-SHA512: 44be8ca11676e64a31b08339cfc2979f3427ca586c391e603aada8d8018c283253a0ee6558ecbfc978240845d3a1c57249f35da2e1f709b12e06a999cbc7753b
This commit is contained in:
commit
683294da1e
@ -26,6 +26,7 @@ pub enum Message {
|
||||
Psbt(Result<Psbt, Error>),
|
||||
Signed(Result<(Psbt, Fingerprint), Error>),
|
||||
Updated(Result<(), Error>),
|
||||
Saved(Result<(), Error>),
|
||||
StartRescan(Result<(), Error>),
|
||||
ConnectedHardwareWallets(Vec<HardwareWallet>),
|
||||
HistoryTransactions(Result<Vec<HistoryTransaction>, Error>),
|
||||
|
||||
@ -12,15 +12,13 @@ use crate::{
|
||||
Daemon,
|
||||
},
|
||||
hw::{list_hardware_wallets, HardwareWallet},
|
||||
ui::component::modal,
|
||||
};
|
||||
|
||||
trait Action {
|
||||
fn warning(&self) -> Option<&Error> {
|
||||
None
|
||||
}
|
||||
fn updated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn load(&self, _daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
Command::none()
|
||||
}
|
||||
@ -40,13 +38,13 @@ pub struct SpendTxState {
|
||||
config: Config,
|
||||
tx: SpendTx,
|
||||
saved: bool,
|
||||
action: Box<dyn Action>,
|
||||
action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
impl SpendTxState {
|
||||
pub fn new(config: Config, tx: SpendTx, saved: bool) -> Self {
|
||||
Self {
|
||||
action: choose_action(&config, saved, &tx),
|
||||
action: None,
|
||||
config,
|
||||
tx,
|
||||
saved,
|
||||
@ -54,7 +52,11 @@ impl SpendTxState {
|
||||
}
|
||||
|
||||
pub fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
self.action.load(daemon)
|
||||
if let Some(action) = &self.action {
|
||||
action.load(daemon)
|
||||
} else {
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
@ -63,60 +65,57 @@ impl SpendTxState {
|
||||
cache: &Cache,
|
||||
message: Message,
|
||||
) -> Command<Message> {
|
||||
let cmd = match &message {
|
||||
match &message {
|
||||
Message::View(view::Message::Spend(msg)) => match msg {
|
||||
view::SpendTxMessage::Cancel => {
|
||||
self.action = choose_action(&self.config, self.saved, &self.tx);
|
||||
self.action.load(daemon.clone())
|
||||
self.action = None;
|
||||
}
|
||||
view::SpendTxMessage::Delete => {
|
||||
self.action = Box::new(DeleteAction::default());
|
||||
self.action.load(daemon.clone())
|
||||
self.action = Some(Box::new(DeleteAction::default()));
|
||||
}
|
||||
view::SpendTxMessage::Sign => {
|
||||
let action = SignAction::new(self.config.clone());
|
||||
let cmd = action.load(daemon);
|
||||
self.action = Some(Box::new(action));
|
||||
return cmd;
|
||||
}
|
||||
view::SpendTxMessage::Broadcast => {
|
||||
self.action = Some(Box::new(BroadcastAction::default()));
|
||||
}
|
||||
view::SpendTxMessage::Save => {
|
||||
self.action = Some(Box::new(SaveAction::default()));
|
||||
}
|
||||
_ => {
|
||||
if let Some(action) = self.action.as_mut() {
|
||||
return action.update(daemon.clone(), cache, message, &mut self.tx);
|
||||
}
|
||||
}
|
||||
_ => self
|
||||
.action
|
||||
.update(daemon.clone(), cache, message, &mut self.tx),
|
||||
},
|
||||
_ => self
|
||||
.action
|
||||
.update(daemon.clone(), cache, message, &mut self.tx),
|
||||
Message::Updated(Ok(_)) => {
|
||||
self.saved = true;
|
||||
if let Some(action) = self.action.as_mut() {
|
||||
return action.update(daemon.clone(), cache, message, &mut self.tx);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(action) = self.action.as_mut() {
|
||||
return action.update(daemon.clone(), cache, message, &mut self.tx);
|
||||
}
|
||||
}
|
||||
};
|
||||
if self.action.updated() {
|
||||
self.saved = true;
|
||||
self.action = choose_action(&self.config, self.saved, &self.tx);
|
||||
self.action.load(daemon)
|
||||
} else {
|
||||
cmd
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
|
||||
pub fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
detail::spend_view(
|
||||
self.action.warning(),
|
||||
&self.tx,
|
||||
self.action.view(),
|
||||
self.saved,
|
||||
cache.network,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_action(config: &Config, saved: bool, tx: &SpendTx) -> Box<dyn Action> {
|
||||
if saved {
|
||||
match tx.status {
|
||||
SpendStatus::Deprecated | SpendStatus::Broadcasted => {
|
||||
return Box::new(NoAction::default());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if !tx.psbt.inputs.first().unwrap().partial_sigs.is_empty() {
|
||||
return Box::new(BroadcastAction::default());
|
||||
let content = detail::spend_view(&self.tx, self.saved, cache.network);
|
||||
if let Some(action) = &self.action {
|
||||
modal::Modal::new(content, action.view())
|
||||
.on_blur(view::Message::Spend(view::SpendTxMessage::Cancel))
|
||||
.into()
|
||||
} else {
|
||||
return Box::new(SignAction::new(config.clone()));
|
||||
content
|
||||
}
|
||||
}
|
||||
Box::new(SaveAction::default())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -126,14 +125,6 @@ pub struct SaveAction {
|
||||
}
|
||||
|
||||
impl Action for SaveAction {
|
||||
fn warning(&self) -> Option<&Error> {
|
||||
self.error.as_ref()
|
||||
}
|
||||
|
||||
fn updated(&self) -> bool {
|
||||
self.saved
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
@ -159,20 +150,17 @@ impl Action for SaveAction {
|
||||
Command::none()
|
||||
}
|
||||
fn view(&self) -> Element<view::Message> {
|
||||
detail::save_action(self.saved)
|
||||
detail::save_action(self.error.as_ref(), self.saved)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BroadcastAction {
|
||||
broadcasted: bool,
|
||||
broadcast: bool,
|
||||
error: Option<Error>,
|
||||
}
|
||||
|
||||
impl Action for BroadcastAction {
|
||||
fn warning(&self) -> Option<&Error> {
|
||||
self.error.as_ref()
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
@ -195,7 +183,10 @@ impl Action for BroadcastAction {
|
||||
);
|
||||
}
|
||||
Message::Updated(res) => match res {
|
||||
Ok(()) => self.broadcasted = true,
|
||||
Ok(()) => {
|
||||
tx.status = SpendStatus::Broadcast;
|
||||
self.broadcast = true;
|
||||
}
|
||||
Err(e) => self.error = Some(e),
|
||||
},
|
||||
_ => {}
|
||||
@ -203,7 +194,7 @@ impl Action for BroadcastAction {
|
||||
Command::none()
|
||||
}
|
||||
fn view(&self) -> Element<view::Message> {
|
||||
detail::broadcast_action(self.broadcasted)
|
||||
detail::broadcast_action(self.error.as_ref(), self.broadcast)
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,10 +205,6 @@ pub struct DeleteAction {
|
||||
}
|
||||
|
||||
impl Action for DeleteAction {
|
||||
fn warning(&self) -> Option<&Error> {
|
||||
self.error.as_ref()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
@ -248,7 +235,7 @@ impl Action for DeleteAction {
|
||||
Command::none()
|
||||
}
|
||||
fn view(&self) -> Element<view::Message> {
|
||||
detail::delete_action(self.deleted)
|
||||
detail::delete_action(self.error.as_ref(), self.deleted)
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,7 +246,6 @@ pub struct SignAction {
|
||||
hws: Vec<HardwareWallet>,
|
||||
error: Option<Error>,
|
||||
signed: Vec<Fingerprint>,
|
||||
updated: bool,
|
||||
}
|
||||
|
||||
impl SignAction {
|
||||
@ -271,7 +257,6 @@ impl SignAction {
|
||||
hws: Vec::new(),
|
||||
error: None,
|
||||
signed: Vec::new(),
|
||||
updated: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,10 +266,6 @@ impl Action for SignAction {
|
||||
self.error.as_ref()
|
||||
}
|
||||
|
||||
fn updated(&self) -> bool {
|
||||
self.updated
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let config = self.config.clone();
|
||||
let desc = daemon.config().main_descriptor.to_string();
|
||||
@ -327,7 +308,7 @@ impl Action for SignAction {
|
||||
}
|
||||
},
|
||||
Message::Updated(res) => match res {
|
||||
Ok(()) => self.updated = true,
|
||||
Ok(()) => self.processing = false,
|
||||
Err(e) => self.error = Some(e),
|
||||
},
|
||||
// We add the new hws without dropping the reference of the previous ones.
|
||||
@ -346,7 +327,13 @@ impl Action for SignAction {
|
||||
Command::none()
|
||||
}
|
||||
fn view(&self) -> Element<view::Message> {
|
||||
view::spend::detail::sign_action(&self.hws, self.processing, self.chosen_hw, &self.signed)
|
||||
view::spend::detail::sign_action(
|
||||
self.error.as_ref(),
|
||||
&self.hws,
|
||||
self.processing,
|
||||
self.chosen_hw,
|
||||
&self.signed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,12 +349,3 @@ async fn sign_psbt(
|
||||
hw.sign_tx(&mut psbt).await.map_err(Error::from)?;
|
||||
Ok((psbt, fingerprint))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NoAction {}
|
||||
|
||||
impl Action for NoAction {
|
||||
fn view(&self) -> Element<view::Message> {
|
||||
iced::widget::Column::new().into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,9 @@ pub enum CreateSpendMessage {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SpendTxMessage {
|
||||
Delete,
|
||||
Sign,
|
||||
Broadcast,
|
||||
Save,
|
||||
Confirm,
|
||||
Cancel,
|
||||
SelectHardwareWallet(usize),
|
||||
|
||||
@ -24,21 +24,14 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
pub fn spend_view<'a, T: Into<Element<'a, Message>>>(
|
||||
warning: Option<&Error>,
|
||||
tx: &'a SpendTx,
|
||||
action: T,
|
||||
show_delete: bool,
|
||||
network: Network,
|
||||
) -> Element<'a, Message> {
|
||||
pub fn spend_view(tx: &SpendTx, saved: bool, network: Network) -> Element<Message> {
|
||||
spend_modal(
|
||||
show_delete,
|
||||
warning,
|
||||
saved,
|
||||
None,
|
||||
Column::new()
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.push(spend_header(tx))
|
||||
.push(action)
|
||||
.push(spend_overview_view(tx))
|
||||
.push(inputs_and_outputs_view(
|
||||
&tx.coins,
|
||||
@ -50,36 +43,44 @@ pub fn spend_view<'a, T: Into<Element<'a, Message>>>(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn save_action<'a>(saved: bool) -> Element<'a, Message> {
|
||||
pub fn save_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Message> {
|
||||
if saved {
|
||||
card::simple(text("Transaction is saved"))
|
||||
.width(Length::Fill)
|
||||
.width(Length::Units(400))
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.into()
|
||||
} else {
|
||||
card::simple(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push(text("Save the transaction"))
|
||||
.push(Row::new().push(Column::new().width(Length::Fill)).push(
|
||||
button::primary(None, "Save").on_press(Message::Spend(SpendTxMessage::Confirm)),
|
||||
)),
|
||||
.push_maybe(warning.map(|w| warn(Some(w))))
|
||||
.push(text("Save the transaction as draft"))
|
||||
.push(
|
||||
Row::new()
|
||||
.push(Column::new().width(Length::Fill))
|
||||
.push(button::alert(None, "Ignore").on_press(Message::Close))
|
||||
.push(
|
||||
button::primary(None, "Save")
|
||||
.on_press(Message::Spend(SpendTxMessage::Confirm)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Units(400))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn broadcast_action<'a>(saved: bool) -> Element<'a, Message> {
|
||||
pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Message> {
|
||||
if saved {
|
||||
card::simple(text("Transaction is broadcasted"))
|
||||
.width(Length::Fill)
|
||||
card::simple(text("Transaction is broadcast"))
|
||||
.width(Length::Units(400))
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.into()
|
||||
} else {
|
||||
card::simple(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push_maybe(warning.map(|w| warn(Some(w))))
|
||||
.push(text("Broadcast the transaction"))
|
||||
.push(
|
||||
Row::new().push(Column::new().width(Length::Fill)).push(
|
||||
@ -88,21 +89,28 @@ pub fn broadcast_action<'a>(saved: bool) -> Element<'a, Message> {
|
||||
),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Units(400))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_action<'a>(deleted: bool) -> Element<'a, Message> {
|
||||
pub fn delete_action<'a>(warning: Option<&Error>, deleted: bool) -> Element<'a, Message> {
|
||||
if deleted {
|
||||
card::simple(text("Transaction is deleted"))
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
card::simple(
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
.push(text("Transaction is deleted"))
|
||||
.push(button::primary(None, "Go back to drafts").on_press(Message::Close)),
|
||||
)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
.width(Length::Units(400))
|
||||
.into()
|
||||
} else {
|
||||
card::simple(
|
||||
Column::new()
|
||||
.spacing(10)
|
||||
.push_maybe(warning.map(|w| warn(Some(w))))
|
||||
.push(text("Delete the transaction draft"))
|
||||
.push(
|
||||
Row::new()
|
||||
@ -117,13 +125,13 @@ pub fn delete_action<'a>(deleted: bool) -> Element<'a, Message> {
|
||||
),
|
||||
),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Units(400))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
|
||||
show_delete: bool,
|
||||
saved: bool,
|
||||
warning: Option<&Error>,
|
||||
content: T,
|
||||
) -> Element<'a, Message> {
|
||||
@ -132,7 +140,7 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
|
||||
.push(
|
||||
Container::new(
|
||||
Row::new()
|
||||
.push(if show_delete {
|
||||
.push(if saved {
|
||||
Column::new()
|
||||
.push(
|
||||
button::alert(Some(icon::trash_icon()), "Delete")
|
||||
@ -147,9 +155,12 @@ pub fn spend_modal<'a, T: Into<Element<'a, Message>>>(
|
||||
.width(Length::Fill)
|
||||
})
|
||||
.align_items(iced::Alignment::Center)
|
||||
.push(
|
||||
button::primary(Some(icon::cross_icon()), "Close").on_press(Message::Close),
|
||||
),
|
||||
.push(if saved {
|
||||
button::primary(Some(icon::cross_icon()), "Close").on_press(Message::Close)
|
||||
} else {
|
||||
button::primary(Some(icon::cross_icon()), "Close")
|
||||
.on_press(Message::Spend(SpendTxMessage::Save))
|
||||
}),
|
||||
)
|
||||
.padding(10)
|
||||
.style(container::Style::Background),
|
||||
@ -185,8 +196,8 @@ fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Simple),
|
||||
),
|
||||
SpendStatus::Broadcasted => Some(
|
||||
Container::new(text(" Broadcasted ").small())
|
||||
SpendStatus::Broadcast => Some(
|
||||
Container::new(text(" Broadcast ").small())
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Success),
|
||||
),
|
||||
@ -225,8 +236,25 @@ fn spend_overview_view<'a>(tx: &SpendTx) -> Element<'a, Message> {
|
||||
.push(text(format!(
|
||||
"{}",
|
||||
tx.psbt.inputs[0].partial_sigs.len(),
|
||||
))),
|
||||
)))
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push_maybe(if tx.status == SpendStatus::Pending {
|
||||
if !tx.is_signed() {
|
||||
Some(
|
||||
button::primary(None, "Sign")
|
||||
.on_press(Message::Spend(SpendTxMessage::Sign)),
|
||||
)
|
||||
} else {
|
||||
Some(
|
||||
button::primary(None, "Broadcast").on_press(
|
||||
Message::Spend(SpendTxMessage::Broadcast),
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20),
|
||||
)
|
||||
@ -471,6 +499,7 @@ pub fn inputs_and_outputs_view<'a>(
|
||||
}
|
||||
|
||||
pub fn sign_action<'a>(
|
||||
warning: Option<&Error>,
|
||||
hws: &[HardwareWallet],
|
||||
processing: bool,
|
||||
chosen_hw: Option<usize>,
|
||||
@ -478,6 +507,7 @@ pub fn sign_action<'a>(
|
||||
) -> Element<'a, Message> {
|
||||
card::simple(
|
||||
Column::new()
|
||||
.push_maybe(warning.map(|w| warn(Some(w))))
|
||||
.push(if !hws.is_empty() {
|
||||
Column::new()
|
||||
.push(
|
||||
@ -520,6 +550,6 @@ pub fn sign_action<'a>(
|
||||
.width(Length::Fill)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.width(Length::Units(500))
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Simple),
|
||||
),
|
||||
SpendStatus::Broadcasted => Some(
|
||||
Container::new(text(" Broadcasted ").small())
|
||||
SpendStatus::Broadcast => Some(
|
||||
Container::new(text(" Broadcast ").small())
|
||||
.padding(3)
|
||||
.style(badge::PillStyle::Success),
|
||||
),
|
||||
|
||||
@ -30,11 +30,11 @@ pub struct SpendTx {
|
||||
pub status: SpendStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SpendStatus {
|
||||
Pending,
|
||||
Deprecated,
|
||||
Broadcasted,
|
||||
Broadcast,
|
||||
}
|
||||
|
||||
impl SpendTx {
|
||||
@ -58,7 +58,7 @@ impl SpendTx {
|
||||
inputs_amount += coin.amount;
|
||||
if let Some(info) = coin.spend_info {
|
||||
if info.txid == psbt.unsigned_tx.txid() {
|
||||
status = SpendStatus::Broadcasted
|
||||
status = SpendStatus::Broadcast
|
||||
} else {
|
||||
status = SpendStatus::Deprecated
|
||||
}
|
||||
@ -74,6 +74,10 @@ impl SpendTx {
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_signed(&self) -> bool {
|
||||
!self.psbt.inputs.first().unwrap().partial_sigs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -4,6 +4,7 @@ pub mod card;
|
||||
pub mod collapse;
|
||||
pub mod container;
|
||||
pub mod form;
|
||||
pub mod modal;
|
||||
pub mod notification;
|
||||
pub mod text;
|
||||
|
||||
|
||||
273
gui/src/ui/component/modal.rs
Normal file
273
gui/src/ui/component/modal.rs
Normal file
@ -0,0 +1,273 @@
|
||||
/// modal widget from https://github.com/iced-rs/iced/blob/master/examples/modal/
|
||||
use iced_native::alignment::Alignment;
|
||||
use iced_native::widget::{self, Tree};
|
||||
use iced_native::{
|
||||
event, layout, mouse, overlay, renderer, Clipboard, Color, Element, Event, Layout, Length,
|
||||
Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
|
||||
/// A widget that centers a modal element over some base element
|
||||
pub struct Modal<'a, Message, Renderer> {
|
||||
base: Element<'a, Message, Renderer>,
|
||||
modal: Element<'a, Message, Renderer>,
|
||||
on_blur: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Modal<'a, Message, Renderer> {
|
||||
/// Returns a new [`Modal`]
|
||||
pub fn new(
|
||||
base: impl Into<Element<'a, Message, Renderer>>,
|
||||
modal: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
base: base.into(),
|
||||
modal: modal.into(),
|
||||
on_blur: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the message that will be produces when the background
|
||||
/// of the [`Modal`] is pressed
|
||||
pub fn on_blur(self, on_blur: Message) -> Self {
|
||||
Self {
|
||||
on_blur: Some(on_blur),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for Modal<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.base), Tree::new(&self.modal)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
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 layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
self.base.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
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.base.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.base.as_widget().draw(
|
||||
&state.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
state: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
Some(overlay::Element::new(
|
||||
layout.position(),
|
||||
Box::new(Overlay {
|
||||
content: &mut self.modal,
|
||||
tree: &mut state.children[1],
|
||||
size: layout.bounds().size(),
|
||||
on_blur: self.on_blur.clone(),
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.base.as_widget().mouse_interaction(
|
||||
&state.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.base
|
||||
.as_widget()
|
||||
.operate(&mut state.children[0], layout, operation);
|
||||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Renderer> {
|
||||
content: &'b mut Element<'a, Message, Renderer>,
|
||||
tree: &'b mut Tree,
|
||||
size: Size,
|
||||
on_blur: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, Message, Renderer> overlay::Overlay<Message, Renderer>
|
||||
for Overlay<'a, 'b, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn layout(&self, renderer: &Renderer, _bounds: Size, position: Point) -> layout::Node {
|
||||
let limits = layout::Limits::new(Size::ZERO, self.size)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let mut child = self.content.as_widget().layout(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);
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let content_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
if let Some(message) = self.on_blur.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = &event {
|
||||
if !content_bounds.contains(cursor_position) {
|
||||
shell.publish(message.clone());
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
self.tree,
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border_radius: renderer::BorderRadius::from(0.0),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color {
|
||||
a: 0.80,
|
||||
..Color::BLACK
|
||||
},
|
||||
);
|
||||
|
||||
self.content.as_widget().draw(
|
||||
self.tree,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
&layout.bounds(),
|
||||
);
|
||||
}
|
||||
|
||||
fn operate(&mut self, layout: Layout<'_>, operation: &mut dyn widget::Operation<Message>) {
|
||||
self.content
|
||||
.as_widget()
|
||||
.operate(self.tree, layout.children().next().unwrap(), operation);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
self.tree,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Modal<'a, Message, Renderer>> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + iced_native::Renderer,
|
||||
Message: 'a + Clone,
|
||||
{
|
||||
fn from(modal: Modal<'a, Message, Renderer>) -> Self {
|
||||
Element::new(modal)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user