use crate::{
AccessLabel, Button, EditBox, Filler, ScrollLabel, SelectableLabel, adapt::AdaptWidgetAny,
};
use kas::prelude::*;
use kas::runner::AppData;
use kas::text::format::FormattableText;
use std::error::Error;
use std::fmt::Write;
#[derive(Copy, Clone, Debug)]
struct MessageBoxOk;
#[impl_self]
mod MessageBox {
#[widget]
#[layout(column! [self.label, self.button])]
pub struct MessageBox<T: FormattableText + 'static> {
core: widget_core!(),
#[widget]
label: SelectableLabel<T>,
#[widget]
button: Button<AccessLabel>,
}
impl Self {
pub fn new(message: T) -> Self {
MessageBox {
core: Default::default(),
label: SelectableLabel::new(message),
button: Button::label_msg("&Ok", MessageBoxOk),
}
}
pub fn into_window<A: AppData>(self, title: impl ToString) -> Window<A> {
Window::new(self.map_any(), title).with_restrictions(true, true)
}
pub fn display(self, cx: &mut EventCx, title: impl ToString) {
cx.add_dataless_window(self.into_window(title), true);
}
}
impl Events for Self {
type Data = ();
fn configure(&mut self, cx: &mut ConfigCx) {
cx.register_nav_fallback(self.id());
}
fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
match event {
Event::Command(Command::Escape, _) | Event::Command(Command::Enter, _) => {
cx.close_own_window();
Used
}
_ => Unused,
}
}
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(MessageBoxOk) = cx.try_pop() {
cx.close_own_window();
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ErrorResult;
#[impl_self]
mod AlertError {
#[widget]
#[layout(column! [
self.label,
self.details,
self.ok,
])]
pub struct AlertError<T: FormattableText + 'static> {
core: widget_core!(),
parent: Id,
title: String,
#[widget]
label: SelectableLabel<T>,
#[widget]
details: ScrollLabel<String>,
#[widget]
ok: Button<AccessLabel>,
}
impl Self {
pub fn new(message: T, error: &dyn Error) -> Self {
let mut details = format!("{error}");
let mut source = error.source();
while let Some(error) = source {
write!(&mut details, "\nCause: {error}").expect("write! to String failed");
source = error.source();
}
AlertError {
core: Default::default(),
parent: Id::default(),
title: "Error".to_string(),
label: SelectableLabel::new(message),
details: ScrollLabel::new(details),
ok: Button::label_msg("&Ok", ErrorResult),
}
}
pub fn with_title(mut self, title: impl ToString) -> Self {
self.title = title.to_string();
self
}
pub fn display_for(mut self, cx: &mut EventCx, parent: Id) {
self.parent = parent;
let title = std::mem::take(&mut self.title);
let window = Window::new(self, title).with_restrictions(true, true);
cx.add_dataless_window(window, true);
}
}
impl Events for Self {
type Data = ();
fn configure(&mut self, cx: &mut ConfigCx) {
cx.register_nav_fallback(self.id());
}
fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
let result = match event {
Event::Command(Command::Escape, _) | Event::Command(Command::Enter, _) => {
ErrorResult
}
_ => return Unused,
};
cx.send(self.parent.clone(), result);
cx.close_own_window();
Used
}
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(result) = cx.try_pop::<ErrorResult>() {
cx.send(self.parent.clone(), result);
cx.close_own_window();
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum UnsavedResult {
Save,
Discard,
Cancel,
}
#[impl_self]
mod AlertUnsaved {
#[widget]
#[layout(column! [
self.label,
row![self.save, self.discard, self.cancel],
])]
pub struct AlertUnsaved<T: FormattableText + 'static> {
core: widget_core!(),
parent: Id,
title: String,
#[widget]
label: SelectableLabel<T>,
#[widget]
save: Button<AccessLabel>,
#[widget]
discard: Button<AccessLabel>,
#[widget]
cancel: Button<AccessLabel>,
}
impl Self {
pub fn new(message: T) -> Self {
AlertUnsaved {
core: Default::default(),
parent: Id::default(),
title: "Unsaved changes".to_string(),
label: SelectableLabel::new(message),
save: Button::label_msg("&Save", UnsavedResult::Save),
discard: Button::label_msg("&Discard", UnsavedResult::Discard),
cancel: Button::label_msg("&Cancel", UnsavedResult::Cancel),
}
}
pub fn with_title(mut self, title: impl ToString) -> Self {
self.title = title.to_string();
self
}
pub fn display_for(mut self, cx: &mut EventCx, parent: Id) {
self.parent = parent;
let title = std::mem::take(&mut self.title);
let window = Window::new(self, title).with_restrictions(true, true);
cx.add_dataless_window(window, true);
}
}
impl Events for Self {
type Data = ();
fn configure(&mut self, cx: &mut ConfigCx) {
cx.register_nav_fallback(self.id());
}
fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
let result = match event {
Event::Command(Command::Escape, _) => UnsavedResult::Cancel,
Event::Command(Command::Enter, _) => {
if let Some(focus) = cx.nav_focus() {
if self.save.is_ancestor_of(focus) {
UnsavedResult::Save
} else if self.discard.is_ancestor_of(focus) {
UnsavedResult::Discard
} else if self.cancel.is_ancestor_of(focus) {
UnsavedResult::Cancel
} else {
return Unused;
}
} else {
return Unused;
}
}
_ => return Unused,
};
cx.send(self.parent.clone(), result);
cx.close_own_window();
Used
}
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(result) = cx.try_pop::<UnsavedResult>() {
cx.send(self.parent.clone(), result);
cx.close_own_window();
}
}
}
}
#[derive(Debug)]
pub enum TextEditResult {
Cancel,
Ok(String),
}
#[derive(Clone, Debug)]
struct MsgClose(bool);
#[impl_self]
mod TextEdit {
#[widget]
#[layout(grid! {
(0..=2, 0) => self.edit,
(0, 1) => Filler::maximize(),
(1, 1) => Button::label_msg("&Cancel", MsgClose(false)),
(2, 1) => Button::label_msg("&Save", MsgClose(true)),
})]
pub struct TextEdit {
core: widget_core!(),
#[widget]
edit: EditBox,
}
impl Self {
pub fn new(text: impl ToString, multi_line: bool) -> Self {
TextEdit {
core: Default::default(),
edit: EditBox::text(text).with_multi_line(multi_line),
}
}
pub fn set_text(&mut self, cx: &mut EventState, text: impl ToString) {
self.edit.clear(cx);
self.edit.set_string(cx, text.to_string());
}
pub fn into_window<A: AppData>(self, title: impl ToString) -> Window<A> {
Window::new(self.map_any(), title)
}
fn close(&mut self, cx: &mut EventCx, commit: bool) -> IsUsed {
cx.push(if commit {
TextEditResult::Ok(self.edit.clone_string())
} else {
TextEditResult::Cancel
});
Used
}
}
impl Events for Self {
type Data = ();
fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
match event {
Event::Command(Command::Escape, _) => self.close(cx, false),
Event::Command(Command::Enter, _) => self.close(cx, true),
_ => Unused,
}
}
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(MsgClose(commit)) = cx.try_pop() {
let _ = self.close(cx, commit);
}
}
}
}