use std::fmt::{self, Debug};
use kas::class::{Editable, HasText};
use kas::draw::TextClass;
use kas::prelude::*;
#[derive(Clone, Debug, PartialEq)]
enum LastEdit {
None,
Insert,
Backspace,
Clear,
Paste,
}
impl Default for LastEdit {
fn default() -> Self {
LastEdit::None
}
}
enum EditAction {
None,
Activate,
Edit,
}
pub type EditBoxVoid = EditBox<EditVoid>;
pub trait EditGuard: Sized {
type Msg;
fn activate(_: &mut EditBox<Self>) -> Option<Self::Msg> {
None
}
fn focus_lost(_: &mut EditBox<Self>) -> Option<Self::Msg> {
None
}
fn edit(_: &mut EditBox<Self>) -> Option<Self::Msg> {
None
}
}
#[derive(Clone, Debug)]
pub struct EditVoid;
impl EditGuard for EditVoid {
type Msg = VoidMsg;
}
pub struct EditActivate<F: Fn(&str) -> Option<M>, M>(pub F);
impl<F: Fn(&str) -> Option<M>, M> EditGuard for EditActivate<F, M> {
type Msg = M;
fn activate(edit: &mut EditBox<Self>) -> Option<Self::Msg> {
(edit.guard.0)(&edit.text)
}
}
pub struct EditAFL<F: Fn(&str) -> Option<M>, M>(pub F);
impl<F: Fn(&str) -> Option<M>, M> EditGuard for EditAFL<F, M> {
type Msg = M;
fn activate(edit: &mut EditBox<Self>) -> Option<Self::Msg> {
(edit.guard.0)(&edit.text)
}
fn focus_lost(edit: &mut EditBox<Self>) -> Option<Self::Msg> {
(edit.guard.0)(&edit.text)
}
}
pub struct EditEdit<F: Fn(&str) -> Option<M>, M>(pub F);
impl<F: Fn(&str) -> Option<M>, M> EditGuard for EditEdit<F, M> {
type Msg = M;
fn edit(edit: &mut EditBox<Self>) -> Option<Self::Msg> {
(edit.guard.0)(&edit.text)
}
}
#[widget(config(key_nav = true, cursor_icon = event::CursorIcon::Text))]
#[handler(handle=noauto, generics = <> where G: EditGuard)]
#[derive(Clone, Default, Widget)]
pub struct EditBox<G: 'static> {
#[widget_core]
core: CoreData,
frame_offset: Coord,
frame_size: Size,
text_rect: Rect,
editable: bool,
multi_line: bool,
text: String,
old_state: Option<String>,
last_edit: LastEdit,
error_state: bool,
pub guard: G,
}
impl<G> Debug for EditBox<G> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"EditBox {{ core: {:?}, editable: {:?}, text: {:?}, ... }}",
self.core, self.editable, self.text
)
}
}
impl<G: 'static> Layout for EditBox<G> {
fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules {
let frame_sides = size_handle.edit_surround();
let inner = size_handle.inner_margin();
let frame_offset = frame_sides.0 + inner;
let frame_size = frame_offset + frame_sides.1 + inner;
let margins = size_handle.outer_margins();
let frame_rules = SizeRules::extract_fixed(axis.is_vertical(), frame_size, margins);
let class = if self.multi_line {
TextClass::EditMulti
} else {
TextClass::Edit
};
let content_rules = size_handle.text_bound(&self.text, class, axis);
let m = content_rules.margins();
let rules = content_rules.surrounded_by(frame_rules, true);
if axis.is_horizontal() {
self.core.rect.size.0 = rules.ideal_size();
self.frame_offset.0 = frame_offset.0 as i32 + m.0 as i32;
self.frame_size.0 = frame_size.0 + (m.0 + m.1) as u32;
} else {
self.core.rect.size.1 = rules.ideal_size();
self.frame_offset.1 = frame_offset.1 as i32 + m.0 as i32;
self.frame_size.1 = frame_size.1 + (m.0 + m.1) as u32;
}
rules
}
fn set_rect(&mut self, rect: Rect, align: AlignHints) {
let valign = if self.multi_line {
Align::Stretch
} else {
Align::Centre
};
let rect = align
.complete(Align::Stretch, valign, self.rect().size)
.apply(rect);
self.core.rect = rect;
self.text_rect.pos = rect.pos + self.frame_offset;
self.text_rect.size = rect.size - self.frame_size;
}
fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) {
let class = if self.multi_line {
TextClass::EditMulti
} else {
TextClass::Edit
};
let mut input_state = self.input_state(mgr, disabled);
input_state.error = self.error_state;
draw_handle.edit_box(self.core.rect, input_state);
let align = (Align::Begin, Align::Begin);
let mut text = &self.text;
let mut _string;
if input_state.char_focus {
_string = self.text.clone();
_string.push('|');
text = &_string;
}
draw_handle.text(self.text_rect, text, class, align);
}
}
impl EditBox<EditVoid> {
pub fn new<S: Into<String>>(text: S) -> Self {
EditBox {
core: Default::default(),
frame_offset: Default::default(),
frame_size: Default::default(),
text_rect: Default::default(),
editable: true,
multi_line: false,
text: text.into(),
old_state: None,
last_edit: LastEdit::None,
error_state: false,
guard: EditVoid,
}
}
pub fn with_guard<G>(self, guard: G) -> EditBox<G> {
EditBox {
core: self.core,
frame_offset: self.frame_offset,
frame_size: self.frame_size,
text_rect: self.text_rect,
editable: self.editable,
multi_line: self.multi_line,
text: self.text,
old_state: self.old_state,
last_edit: self.last_edit,
error_state: self.error_state,
guard,
}
}
pub fn on_activate<F: Fn(&str) -> Option<M>, M>(self, f: F) -> EditBox<EditActivate<F, M>> {
self.with_guard(EditActivate(f))
}
pub fn on_afl<F: Fn(&str) -> Option<M>, M>(self, f: F) -> EditBox<EditAFL<F, M>> {
self.with_guard(EditAFL(f))
}
pub fn on_edit<F: Fn(&str) -> Option<M>, M>(self, f: F) -> EditBox<EditEdit<F, M>> {
self.with_guard(EditEdit(f))
}
}
impl<G> EditBox<G> {
pub fn editable(mut self, editable: bool) -> Self {
self.editable = editable;
self
}
pub fn multi_line(mut self, multi_line: bool) -> Self {
self.multi_line = multi_line;
self
}
pub fn has_error(&self) -> bool {
self.error_state
}
pub fn set_error_state(&mut self, error_state: bool) {
self.error_state = error_state;
}
fn received_char(&mut self, mgr: &mut Manager, c: char) -> EditAction {
if !self.editable {
return EditAction::None;
}
if c < '\u{20}' || (c >= '\u{7f}' && c <= '\u{9f}') {
match c {
'\u{03}' => {
mgr.set_clipboard((&self.text).into());
}
'\u{08}' => {
if self.last_edit != LastEdit::Backspace {
self.old_state = Some(self.text.clone());
self.last_edit = LastEdit::Backspace;
}
self.text.pop();
}
'\u{09}' => (),
'\u{0A}' => (),
'\u{0B}' => (),
'\u{0C}' => (),
'\u{0D}' => return EditAction::Activate,
'\u{16}' => {
if self.last_edit != LastEdit::Paste {
self.old_state = Some(self.text.clone());
self.last_edit = LastEdit::Paste;
}
if let Some(content) = mgr.get_clipboard() {
let mut end = content.len();
for (i, b) in content.as_bytes().iter().cloned().enumerate() {
if b < 0x20 || (b >= 0x7f && b <= 0x9f) {
end = i;
break;
}
}
self.text.push_str(&content[0..end]);
}
}
'\u{1A}' => {
if let Some(state) = self.old_state.as_mut() {
std::mem::swap(state, &mut self.text);
self.last_edit = LastEdit::None;
}
}
'\u{1B}' => (),
'\u{7f}' => {
if self.last_edit != LastEdit::Clear {
self.old_state = Some(self.text.clone());
self.last_edit = LastEdit::Clear;
}
self.text.clear();
}
_ => (),
};
} else {
if self.last_edit != LastEdit::Insert {
self.old_state = Some(self.text.clone());
self.last_edit = LastEdit::Insert;
}
self.text.push(c);
}
mgr.redraw(self.id());
EditAction::Edit
}
}
impl<G> HasText for EditBox<G> {
fn get_text(&self) -> &str {
&self.text
}
fn set_cow_string(&mut self, text: CowString) -> TkAction {
self.text = text.to_string();
TkAction::Redraw
}
}
impl<G> Editable for EditBox<G> {
fn is_editable(&self) -> bool {
self.editable
}
fn set_editable(&mut self, editable: bool) {
self.editable = editable;
}
}
impl<G: EditGuard + 'static> event::Handler for EditBox<G> {
type Msg = G::Msg;
#[inline]
fn activation_via_press(&self) -> bool {
true
}
fn handle(&mut self, mgr: &mut Manager, event: Event) -> Response<Self::Msg> {
match event {
Event::Activate => {
mgr.request_char_focus(self.id());
Response::None
}
Event::LostCharFocus => {
let r = G::focus_lost(self);
r.map(|msg| msg.into()).unwrap_or(Response::None)
}
Event::ReceivedCharacter(c) => {
let r = match self.received_char(mgr, c) {
EditAction::None => None,
EditAction::Activate => G::activate(self),
EditAction::Edit => G::edit(self),
};
r.map(|msg| msg.into()).unwrap_or(Response::None)
}
event => Response::Unhandled(event),
}
}
}