mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 21:30:03 -04:00
Basic text input support
This commit is contained in:
@@ -149,15 +149,16 @@ fn font_definitions_ui(font_definitions: &mut FontDefinitions, region: &mut Regi
|
||||
impl RawInput {
|
||||
pub fn ui(&self, region: &mut Region) {
|
||||
// TODO: simpler way to show values, e.g. `region.value("Mouse Pos:", self.mouse_pos);
|
||||
// TODO: easily change default font!
|
||||
region.add(label!("mouse_down: {}", self.mouse_down));
|
||||
region.add(label!("mouse_pos: {:.1?}", self.mouse_pos));
|
||||
region.add(label!("scroll_delta: {:?}", self.scroll_delta));
|
||||
region.add(label!("screen_size: {:?}", self.screen_size));
|
||||
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
|
||||
region.add(label!("time: {:.3} s", self.time));
|
||||
region.add(label!("text: {:?}", self.text));
|
||||
// region.add(label!("dropped_files: {}", self.dropped_files));
|
||||
// region.add(label!("hovered_files: {}", self.hovered_files));
|
||||
region.add(label!("events: {:?}", self.events));
|
||||
region.add(label!("dropped_files: {:?}", self.dropped_files));
|
||||
region.add(label!("hovered_files: {:?}", self.hovered_files));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,8 +173,8 @@ impl GuiInput {
|
||||
region.add(label!("screen_size: {:?}", self.screen_size));
|
||||
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
|
||||
region.add(label!("time: {}", self.time));
|
||||
region.add(label!("text: {:?}", self.text));
|
||||
// region.add(label!("dropped_files: {}", self.dropped_files));
|
||||
// region.add(label!("hovered_files: {}", self.hovered_files));
|
||||
region.add(label!("events: {:?}", self.events));
|
||||
region.add(label!("dropped_files: {:?}", self.dropped_files));
|
||||
region.add(label!("hovered_files: {:?}", self.hovered_files));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub struct ExampleApp {
|
||||
checked: bool,
|
||||
count: usize,
|
||||
radio: usize,
|
||||
text_inputs: [String; 3],
|
||||
|
||||
size: Vec2,
|
||||
corner_radius: f32,
|
||||
@@ -24,6 +25,8 @@ impl Default for ExampleApp {
|
||||
checked: true,
|
||||
radio: 0,
|
||||
count: 0,
|
||||
text_inputs: Default::default(),
|
||||
|
||||
size: vec2(100.0, 50.0),
|
||||
corner_radius: 5.0,
|
||||
stroke_width: 2.0,
|
||||
@@ -52,7 +55,7 @@ impl ExampleApp {
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Widgets")
|
||||
// .default_open()
|
||||
.default_open()
|
||||
.show(region, |region| {
|
||||
region.horizontal(Align::Min, |region| {
|
||||
region.add(label!("Text can have").text_color(srgba(110, 255, 110, 255)));
|
||||
@@ -94,6 +97,13 @@ impl ExampleApp {
|
||||
if region.add(Button::new("Double it")).clicked {
|
||||
self.slider_value *= 2;
|
||||
}
|
||||
|
||||
for (i, text) in self.text_inputs.iter_mut().enumerate() {
|
||||
region.horizontal(Align::Min, |region|{
|
||||
region.add(label!("Text input {}: ", i));
|
||||
region.add(TextEdit::new(text).id(i));
|
||||
}); // TODO: .tooltip_text("Enter text to edit me")
|
||||
}
|
||||
});
|
||||
|
||||
region.collapsing("Layouts", |region| {
|
||||
@@ -151,7 +161,7 @@ impl ExampleApp {
|
||||
.show(region, |region| self.painting.ui(region));
|
||||
|
||||
CollapsingHeader::new("Resize")
|
||||
.default_open()
|
||||
// .default_open()
|
||||
.show(region, |region| {
|
||||
Resize::default()
|
||||
.default_height(200.0)
|
||||
|
||||
@@ -40,4 +40,5 @@ pub use {
|
||||
style::Style,
|
||||
texture_atlas::Texture,
|
||||
types::*,
|
||||
widgets::Widget,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,9 @@ pub struct Memory {
|
||||
/// The widget being interacted with (e.g. dragged, in case of a slider).
|
||||
pub(crate) active_id: Option<Id>,
|
||||
|
||||
/// The widget with keyboard focus (i.e. a text input field).
|
||||
pub(crate) kb_focus_id: Option<Id>,
|
||||
|
||||
// states of various types of widgets
|
||||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
||||
|
||||
@@ -143,6 +143,10 @@ impl Region {
|
||||
self.ctx.memory.lock()
|
||||
}
|
||||
|
||||
pub fn output(&self) -> parking_lot::MutexGuard<Output> {
|
||||
self.ctx.output.lock()
|
||||
}
|
||||
|
||||
pub fn fonts(&self) -> &Fonts {
|
||||
&*self.ctx.fonts
|
||||
}
|
||||
@@ -281,7 +285,7 @@ impl Region {
|
||||
};
|
||||
add_contents(&mut child_region);
|
||||
let size = child_region.bounding_size();
|
||||
self.reserve_space_without_padding(size);
|
||||
self.reserve_space(size, None);
|
||||
}
|
||||
|
||||
/// Start a region with horizontal layout
|
||||
@@ -356,6 +360,14 @@ impl Region {
|
||||
self.ctx.contains_mouse(self.layer, &self.clip_rect, rect)
|
||||
}
|
||||
|
||||
pub fn has_kb_focus(&self, id: Id) -> bool {
|
||||
self.memory().kb_focus_id == Some(id)
|
||||
}
|
||||
|
||||
pub fn request_kb_focus(&self, id: Id) {
|
||||
self.memory().kb_focus_id = Some(id);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
pub fn add(&mut self, widget: impl Widget) -> GuiResponse {
|
||||
|
||||
@@ -28,6 +28,9 @@ pub struct Style {
|
||||
/// For stuff like check marks in check boxes.
|
||||
pub line_width: f32,
|
||||
|
||||
pub cursor_blink_hz: f32,
|
||||
pub text_cursor_width: f32,
|
||||
|
||||
// TODO: add ability to disable animations!
|
||||
/// How many seconds a typical animation should last
|
||||
pub animation_time: f32,
|
||||
@@ -50,6 +53,8 @@ impl Default for Style {
|
||||
clickable_diameter: 22.0,
|
||||
start_icon_width: 16.0,
|
||||
line_width: 1.0,
|
||||
cursor_blink_hz: 1.0,
|
||||
text_cursor_width: 2.0,
|
||||
animation_time: 1.0 / 20.0,
|
||||
window: Window::default(),
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ pub struct RawInput {
|
||||
/// Time in seconds. Relative to whatever. Used for animation.
|
||||
pub time: f64,
|
||||
|
||||
/// Text input, e.g. via keyboard or paste action
|
||||
pub text: String,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Someone is threatening to drop these on us.
|
||||
pub hovered_files: Vec<std::path::PathBuf>,
|
||||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
}
|
||||
|
||||
/// What the gui maintains
|
||||
/// What emigui maintains
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GuiInput {
|
||||
/// Is the button currently down?
|
||||
@@ -73,14 +73,52 @@ pub struct GuiInput {
|
||||
/// Time in seconds. Relative to whatever. Used for animation.
|
||||
pub time: f64,
|
||||
|
||||
/// Text input, e.g. via keyboard or paste action
|
||||
pub text: String,
|
||||
|
||||
/// Files has been dropped into the window.
|
||||
pub dropped_files: Vec<std::path::PathBuf>,
|
||||
|
||||
/// Someone is threatening to drop these on us.
|
||||
pub hovered_files: Vec<std::path::PathBuf>,
|
||||
|
||||
/// In-order events received this frame
|
||||
pub events: Vec<Event>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Event {
|
||||
Copy,
|
||||
Cut,
|
||||
/// Text input, e.g. via keyboard or paste action
|
||||
Text(String),
|
||||
Key {
|
||||
key: Key,
|
||||
pressed: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Key {
|
||||
Alt,
|
||||
Backspace,
|
||||
Control,
|
||||
Delete,
|
||||
Down,
|
||||
End,
|
||||
Escape,
|
||||
Home,
|
||||
Insert,
|
||||
Left,
|
||||
/// Windows key or Mac Command key
|
||||
Logo,
|
||||
PageDown,
|
||||
PageUp,
|
||||
Return,
|
||||
Right,
|
||||
Shift,
|
||||
// Space,
|
||||
Tab,
|
||||
Up,
|
||||
}
|
||||
|
||||
impl GuiInput {
|
||||
@@ -99,9 +137,9 @@ impl GuiInput {
|
||||
screen_size: new.screen_size,
|
||||
pixels_per_point: new.pixels_per_point,
|
||||
time: new.time,
|
||||
text: new.text.clone(),
|
||||
dropped_files: new.dropped_files.clone(),
|
||||
hovered_files: new.hovered_files.clone(),
|
||||
events: new.events.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,8 +147,12 @@ impl GuiInput {
|
||||
#[derive(Clone, Default, Serialize)]
|
||||
pub struct Output {
|
||||
pub cursor_icon: CursorIcon,
|
||||
|
||||
/// If set, open this url.
|
||||
pub open_url: Option<String>,
|
||||
|
||||
/// Response to Event::Copy or Event::Cut. Ignore if empty.
|
||||
pub copied_text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
@@ -120,6 +162,7 @@ pub enum CursorIcon {
|
||||
/// Pointing hand, used for e.g. web links
|
||||
PointingHand,
|
||||
ResizeNwSe,
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Default for CursorIcon {
|
||||
|
||||
@@ -7,6 +7,9 @@ use crate::{
|
||||
*,
|
||||
};
|
||||
|
||||
mod text_edit;
|
||||
pub use text_edit::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Anything implementing Widget can be added to a Region with Region::add
|
||||
@@ -336,7 +339,6 @@ impl<'a> Slider<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use range syntax
|
||||
pub fn f32(value: &'a mut f32, range: RangeInclusive<f32>) -> Self {
|
||||
Slider {
|
||||
precision: 3,
|
||||
|
||||
113
emigui/src/widgets/text_edit.rs
Normal file
113
emigui/src/widgets/text_edit.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextEdit<'t> {
|
||||
text: &'t mut String,
|
||||
id: Option<Id>,
|
||||
text_style: TextStyle, // TODO: Option<TextStyle>, where None means "use the default for the region"
|
||||
text_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<'t> TextEdit<'t> {
|
||||
pub fn new(text: &'t mut String) -> Self {
|
||||
TextEdit {
|
||||
text,
|
||||
id: None,
|
||||
text_style: TextStyle::Body,
|
||||
text_color: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(mut self, id_source: impl std::hash::Hash) -> Self {
|
||||
self.id = Some(Id::new(id_source));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text_style = text_style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text_color(mut self, text_color: Color) -> Self {
|
||||
self.text_color = Some(text_color);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> Widget for TextEdit<'t> {
|
||||
fn add_to(self, region: &mut Region) -> GuiResponse {
|
||||
let id = region.make_child_id(self.id);
|
||||
|
||||
let font = ®ion.fonts()[self.text_style];
|
||||
let line_spacing = font.line_spacing();
|
||||
let (text, text_size) = font.layout_multiline(self.text.as_str(), region.available_width());
|
||||
let desired_size = text_size.max(vec2(region.available_width(), line_spacing));
|
||||
let interact = region.reserve_space(desired_size, Some(id));
|
||||
|
||||
if interact.clicked {
|
||||
region.request_kb_focus(id);
|
||||
}
|
||||
if interact.hovered {
|
||||
region.output().cursor_icon = CursorIcon::Text;
|
||||
}
|
||||
let has_kb_focus = region.has_kb_focus(id);
|
||||
|
||||
if has_kb_focus {
|
||||
for event in ®ion.input().events {
|
||||
match event {
|
||||
Event::Copy | Event::Cut => {
|
||||
// TODO: cut
|
||||
region.ctx().output.lock().copied_text = self.text.clone();
|
||||
}
|
||||
Event::Text(text) => {
|
||||
if text == "\u{7f}" {
|
||||
// backspace
|
||||
} else {
|
||||
*self.text += text;
|
||||
}
|
||||
}
|
||||
Event::Key { key, pressed: true } => {
|
||||
match key {
|
||||
Key::Backspace => {
|
||||
self.text.pop(); // TODO: unicode aware
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region.add_paint_cmd(PaintCmd::Rect {
|
||||
rect: interact.rect,
|
||||
corner_radius: 0.0,
|
||||
// fill_color: Some(color::BLACK),
|
||||
fill_color: region.style().interact_fill_color(&interact),
|
||||
// fill_color: Some(region.style().background_fill_color()),
|
||||
outline: None, //Some(Outline::new(1.0, color::WHITE)),
|
||||
});
|
||||
|
||||
if has_kb_focus {
|
||||
let cursor_blink_hz = region.style().cursor_blink_hz;
|
||||
let show_cursor =
|
||||
(region.input().time * cursor_blink_hz as f64 * 3.0).floor() as i64 % 3 != 0;
|
||||
if show_cursor {
|
||||
let cursor_pos = if let Some(last) = text.last() {
|
||||
interact.rect.min + vec2(last.max_x(), last.y_offset)
|
||||
} else {
|
||||
interact.rect.min
|
||||
};
|
||||
region.add_paint_cmd(PaintCmd::line_segment(
|
||||
(cursor_pos, cursor_pos + vec2(0.0, line_spacing)),
|
||||
color::WHITE,
|
||||
region.style().text_cursor_width,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
region.add_text(interact.rect.min, self.text_style, text, self.text_color);
|
||||
|
||||
region.response(interact)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user