use crate::*;
use super::text_edit::{apply_text_input, ReturnBehavior};
#[derive(Clone)]
pub struct Textbox {
pub buf: String,
pub cursor: usize,
pub font: FontChoice,
pub opt: WidgetOption,
pub bopt: WidgetBehaviourOption,
}
impl Textbox {
pub fn new(buf: impl Into<String>) -> Self {
let buf = buf.into();
let cursor = buf.len();
Self {
buf,
cursor,
font: FontChoice::default(),
opt: WidgetOption::NONE,
bopt: WidgetBehaviourOption::NONE,
}
}
pub fn with_opt(buf: impl Into<String>, opt: WidgetOption) -> Self {
let buf = buf.into();
let cursor = buf.len();
Self {
buf,
cursor,
font: FontChoice::default(),
opt,
bopt: WidgetBehaviourOption::NONE,
}
}
fn preferred_size_widget(&self, style: &Style, atlas: &AtlasHandle, avail: Dimensioni) -> Dimensioni {
let padding = style.padding.max(0);
let vertical_pad = (padding / 2).max(1);
let font = style.resolve_font_choice(self.font);
let font_height = atlas.get_font_height(font) as i32;
let text_w = if self.buf.is_empty() {
0
} else {
atlas.get_text_size(font, self.buf.as_str()).width
};
let mut width = (text_w + padding * 2 + 1).max(0);
if avail.width > 0 {
width = width.min(avail.width.max(0));
}
let height = (font_height + vertical_pad * 2).max(0);
Dimensioni::new(width, height)
}
fn handle_widget(&mut self, ctx: &mut WidgetCtx<'_>, control: &ControlState) -> ResourceState {
let font = ctx.style().resolve_font_choice(self.font);
textbox_handle(ctx, control, &mut self.buf, &mut self.cursor, self.opt, font)
}
}
pub(crate) fn textbox_handle(
ctx: &mut WidgetCtx<'_>,
control: &ControlState,
buf: &mut String,
cursor: &mut usize,
opt: WidgetOption,
font: FontId,
) -> ResourceState {
let mut res = ResourceState::NONE;
let r = ctx.rect();
if !control.focused {
*cursor = buf.len();
}
let mut cursor_pos = (*cursor).min(buf.len());
let (mouse_pressed, mouse_pos, end_pressed, edit) = {
let input = ctx.input_or_default();
let edit = if control.focused {
apply_text_input(buf, cursor_pos, input, false, ReturnBehavior::Submit)
} else {
super::text_edit::TextEditOutcome {
cursor: cursor_pos,
changed: false,
moved: false,
submit: false,
}
};
(input.mouse_pressed, input.mouse_pos, input.key_code_pressed.is_end(), edit)
};
if control.focused {
cursor_pos = edit.cursor;
if edit.changed {
res |= ResourceState::CHANGE;
}
if edit.submit {
res |= ResourceState::SUBMIT;
}
if end_pressed {
cursor_pos = buf.len();
}
}
if edit.submit {
ctx.clear_focus();
}
ctx.draw_widget_frame(control, r, ControlColor::Base, opt);
let line_height = ctx.atlas().get_font_height(font) as i32;
let baseline = ctx.atlas().get_font_baseline(font);
let descent = (line_height - baseline).max(0);
let mut texty = r.y + r.height / 2 - line_height / 2;
if texty < r.y {
texty = r.y;
}
let max_texty = (r.y + r.height - line_height).max(r.y);
if texty > max_texty {
texty = max_texty;
}
let baseline_y = texty + line_height - descent;
let text_metrics = ctx.atlas().get_text_size(font, buf.as_str());
let padding = ctx.style().padding;
let ofx = r.width - padding - text_metrics.width - 1;
let textx = r.x + if ofx < padding { ofx } else { padding };
if control.focused && mouse_pressed.is_left() && ctx.mouse_over(r) {
let click_x = mouse_pos.x - (textx - r.x);
if click_x <= 0 {
cursor_pos = 0;
} else {
let mut last_width = 0;
let mut new_cursor = buf.len();
for (idx, ch) in buf.char_indices() {
let next = idx + ch.len_utf8();
let width = ctx.atlas().get_text_size(font, &buf[..next]).width;
if click_x < width {
if click_x < (last_width + width) / 2 {
new_cursor = idx;
} else {
new_cursor = next;
}
break;
}
last_width = width;
}
cursor_pos = new_cursor.min(buf.len());
}
}
cursor_pos = cursor_pos.min(buf.len());
*cursor = cursor_pos;
let caret_offset = if cursor_pos == 0 {
0
} else {
ctx.atlas().get_text_size(font, &buf[..cursor_pos]).width
};
if control.focused {
let color = ctx.style().colors[ControlColor::Text as usize];
ctx.push_clip_rect(r);
ctx.draw_text(font, buf.as_str(), vec2(textx, texty), color);
let caret_top = (baseline_y - baseline + 2).max(r.y).min(r.y + r.height);
let caret_bottom = (baseline_y + descent - 2).max(r.y).min(r.y + r.height);
let caret_height = (caret_bottom - caret_top).max(1);
ctx.draw_rect(rect(textx + caret_offset, caret_top, 1, caret_height), color);
ctx.pop_clip_rect();
} else {
ctx.draw_control_text_with_font(font, buf.as_str(), r, ControlColor::Text, opt);
}
res
}
impl Widget for Textbox {
fn widget_opt(&self) -> &WidgetOption {
&self.opt
}
fn behaviour_opt(&self) -> &WidgetBehaviourOption {
&self.bopt
}
fn measure(&self, style: &Style, atlas: &AtlasHandle, avail: Dimensioni) -> Dimensioni {
self.preferred_size_widget(style, atlas, avail)
}
fn run(&mut self, ctx: &mut WidgetCtx<'_>, control: &ControlState) -> ResourceState {
let old_buf = self.buf.clone();
let old_cursor = self.cursor;
let mut res = self.handle_widget(ctx, control);
let changed = self.buf != old_buf || self.cursor != old_cursor;
if control.focused || changed {
res |= ResourceState::ACTIVE;
}
res
}
fn effective_widget_opt(&self) -> WidgetOption {
self.opt | WidgetOption::HOLD_FOCUS
}
fn needs_input_snapshot(&self) -> bool {
true
}
}