Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tab identation for multiline text edit #246

Merged
merged 42 commits into from
May 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
86c4b76
Add multiline TextEdit to the demo
DrOptix Mar 21, 2021
2f806a8
Lock focus on multiline text edit
DrOptix Mar 22, 2021
94c1801
When TAB is detected add 4 Space chars
DrOptix Mar 23, 2021
ba12f9e
Implement tabs as spaces
DrOptix Mar 23, 2021
13ed86d
Sync with work done upstream
DrOptix Mar 23, 2021
19e2608
Make the "tab-as-character" behaviour opt-in.
DrOptix Mar 24, 2021
b3aa498
Adapt the URL open command to the platform
DrOptix Mar 24, 2021
5557b17
Add code editor shortuct functions
DrOptix Mar 24, 2021
cb66aaa
Apply partial suggestions from code review
DrOptix Mar 24, 2021
1d271d5
Remove identation using `shift`+`tab`.
DrOptix Mar 27, 2021
b15a5c7
Single line identation removal fix + tests
DrOptix Mar 27, 2021
724901e
Fix indentation insert in tab as spaces mode
DrOptix Mar 27, 2021
f2ddd8f
Fix tab rendering
DrOptix Mar 28, 2021
4633084
WIP: Rework identation management
DrOptix Mar 28, 2021
5e6fc2d
Refactor: start from clean slate
DrOptix Mar 28, 2021
aafcc03
Refactor: handle identation insert at cursor
DrOptix Mar 29, 2021
d7f3805
Refactor: replace selection in paragraph with identation
DrOptix Mar 29, 2021
939733b
Refactor: Decrease identation
DrOptix Mar 31, 2021
4186356
Refactor: keep selection offsets
DrOptix Apr 1, 2021
b386599
Additional identation management
DrOptix Apr 1, 2021
5bc7857
Fix selection update in tabs-as-tabs mode
DrOptix Apr 1, 2021
7065e4e
Merge branch 'feature/73_identation_text_edit_widget_refactor' into f…
DrOptix Apr 1, 2021
798748d
cargo fmt
DrOptix Apr 1, 2021
c81132f
Sync with upstream
DrOptix Apr 1, 2021
da2392c
Sync with upstream
DrOptix Apr 1, 2021
9e6db0f
Fix build: add missing tab_glyph_info_cache
DrOptix Apr 1, 2021
30fcc1e
Fix identation management in tabs-as-tabs mode
DrOptix Apr 1, 2021
0220a04
Add Fedora command to install wasm-strip
DrOptix Apr 1, 2021
18429f0
Implement identation deletion with backspace
DrOptix Apr 2, 2021
1272ad9
Rework identation management
DrOptix Apr 2, 2021
d468dc7
Use `CodingConfig` to customize code editor
DrOptix Apr 2, 2021
061b56b
Sync with upstream
DrOptix Apr 2, 2021
e14c549
Sync with upstream
DrOptix Apr 2, 2021
04b8cfb
Remove `ccursor_paragraph_end` as it is no longer used
DrOptix Apr 2, 2021
78ac783
Sync with upstream
DrOptix Apr 3, 2021
1b3b5a4
Merge branch 'master' into feature/73_identation_text_edit_widget
DrOptix Apr 6, 2021
4396c87
Sync with upstream
DrOptix Apr 17, 2021
ae4ea45
Sync with upstream
DrOptix Apr 17, 2021
f384003
Sync with upstream
DrOptix Apr 26, 2021
0fe4cce
Sync with upstream
DrOptix Apr 26, 2021
c57a147
Cleanup according to PR reviews
DrOptix Apr 26, 2021
de7a474
Tabs are always rendered as 4 spaces no matter where they are
DrOptix Apr 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions egui/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,12 @@ pub(crate) struct Focus {
/// The last widget interested in focus.
last_interested: Option<Id>,

/// If `true`, pressing tab will NOT move focus away from the current widget.
is_focus_locked: bool,

/// Set at the beginning of the frame, set to `false` when "used".
pressed_tab: bool,

/// Set at the beginning of the frame, set to `false` when "used".
pressed_shift_tab: bool,
}
Expand Down Expand Up @@ -199,6 +203,7 @@ impl Focus {
}
) {
self.id = None;
self.is_focus_locked = false;
break;
}

Expand All @@ -208,10 +213,12 @@ impl Focus {
modifiers,
} = event
{
if modifiers.shift {
self.pressed_shift_tab = true;
} else {
self.pressed_tab = true;
if !self.is_focus_locked {
if modifiers.shift {
self.pressed_shift_tab = true;
} else {
self.pressed_tab = true;
}
}
}
}
Expand All @@ -238,11 +245,11 @@ impl Focus {
self.id = Some(id);
self.give_to_next = false;
} else if self.id == Some(id) {
if self.pressed_tab {
if self.pressed_tab && !self.is_focus_locked {
self.id = None;
self.give_to_next = true;
self.pressed_tab = false;
} else if self.pressed_shift_tab {
} else if self.pressed_shift_tab && !self.is_focus_locked {
self.id_next_frame = self.last_interested; // frame-delay so gained_focus works
self.pressed_shift_tab = false;
}
Expand Down Expand Up @@ -302,11 +309,26 @@ impl Memory {
self.interaction.focus.id == Some(id)
}

pub(crate) fn lock_focus(&mut self, id: Id, b: bool) {
if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked = b;
}
}

pub(crate) fn has_lock_focus(&mut self, id: Id) -> bool {
if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked
} else {
false
}
}

/// Give keyboard focus to a specific widget.
/// See also [`crate::Response::request_focus`].
#[inline(always)]
pub fn request_focus(&mut self, id: Id) {
self.interaction.focus.id = Some(id);
self.interaction.focus.is_focus_locked = false;
}

/// Surrender keyboard focus for a specific widget.
Expand All @@ -315,6 +337,7 @@ impl Memory {
pub fn surrender_focus(&mut self, id: Id) {
if self.interaction.focus.id == Some(id) {
self.interaction.focus.id = None;
self.interaction.focus.is_focus_locked = false;
}
}

Expand Down
16 changes: 16 additions & 0 deletions egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,22 @@ impl Ui {
TextEdit::multiline(text).ui(self)
}

/// A `TextEdit` for code editing.
///
/// This will be multiline, monospace, and will insert tabs instead of moving focus.
///
/// See also [`TextEdit::code_editor`].
pub fn code_editor(&mut self, text: &mut String) -> Response {
self.add(TextEdit::multiline(text).code_editor())
}

/// A `TextEdit` for code editing with configurable `Tab` management.
///
/// Se also [`TextEdit::code_editor_with_config`].
pub fn code_editor_with_config(&mut self, text: &mut String, config: CodingConfig) -> Response {
self.add(TextEdit::multiline(text).code_editor_with_config(config))
}

/// Usage: `if ui.button("Click me").clicked() { … }`
///
/// Shortcut for `add(Button::new(text))`
Expand Down
54 changes: 54 additions & 0 deletions egui/src/widgets/text_edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ impl CCursorPair {
}
}

#[derive(Clone, Copy, Debug, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct CodingConfig {
pub tab_moves_focus: bool,
}

/// A text region that the user can edit the contents of.
///
/// See also [`Ui::text_edit_singleline`] and [`Ui::text_edit_multiline`].
Expand Down Expand Up @@ -140,6 +146,7 @@ pub struct TextEdit<'t> {
enabled: bool,
desired_width: Option<f32>,
desired_height_rows: usize,
tab_moves_focus: bool,
}
impl<'t> TextEdit<'t> {
pub fn cursor(ui: &Ui, id: Id) -> Option<CursorPair> {
Expand Down Expand Up @@ -171,6 +178,7 @@ impl<'t> TextEdit<'t> {
enabled: true,
desired_width: None,
desired_height_rows: 1,
tab_moves_focus: true,
}
}

Expand All @@ -189,9 +197,37 @@ impl<'t> TextEdit<'t> {
enabled: true,
desired_width: None,
desired_height_rows: 4,
tab_moves_focus: true,
}
}

/// When this is true, then pass focus to the next
/// widget.
pub fn tab_moves_focus(mut self, b: bool) -> Self {
self.tab_moves_focus = b;
self
}

/// Build a `TextEdit` focused on code editing.
/// By default it comes with:
/// - monospaced font
/// - focus lock
pub fn code_editor(self) -> Self {
self.text_style(TextStyle::Monospace).tab_moves_focus(false)
}

/// Build a `TextEdit` focused on code editing with configurable `Tab` management.
///
/// Shortcut for:
/// ```rust, ignore
/// egui::TextEdit::multiline(code_snippet)
/// .code_editor()
/// .tab_moves_focus(tab_moves_focus);
/// ```
pub fn code_editor_with_config(self, config: CodingConfig) -> Self {
self.code_editor().tab_moves_focus(config.tab_moves_focus)
}

pub fn id(mut self, id: Id) -> Self {
self.id = Some(id);
self
Expand Down Expand Up @@ -311,6 +347,7 @@ impl<'t> TextEdit<'t> {
enabled,
desired_width,
desired_height_rows,
tab_moves_focus,
} = self;

let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
Expand Down Expand Up @@ -416,6 +453,8 @@ impl<'t> TextEdit<'t> {

let mut text_cursor = None;
if ui.memory().has_focus(id) && enabled {
ui.memory().lock_focus(id, !tab_moves_focus);

let mut cursorp = state
.cursorp
.map(|cursorp| {
Expand Down Expand Up @@ -465,12 +504,27 @@ impl<'t> TextEdit<'t> {
&& text_to_insert != "\r"
{
let mut ccursor = delete_selected(text, &cursorp);

insert_text(&mut ccursor, text, text_to_insert);
Some(CCursorPair::one(ccursor))
} else {
None
}
}
Event::Key {
key: Key::Tab,
pressed: true,
..
} => {
if multiline && ui.memory().has_lock_focus(id) {
let mut ccursor = delete_selected(text, &cursorp);

insert_text(&mut ccursor, text, "\t");
Some(CCursorPair::one(ccursor))
} else {
None
}
}
Event::Key {
key: Key::Enter,
pressed: true,
Expand Down
46 changes: 46 additions & 0 deletions egui_demo_lib/src/apps/demo/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ pub struct Widgets {
radio: Enum,
angle: f32,
color: Color32,
show_password: bool,
single_line_text_input: String,
multiline_text_input: String,
tab_moves_focus: bool,
code_snippet: String,
}

impl Default for Widgets {
Expand All @@ -35,7 +38,31 @@ impl Default for Widgets {
angle: std::f32::consts::TAU / 3.0,
color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(),
single_line_text_input: "Hello World!".to_owned(),
show_password: false,
tab_moves_focus: false,

multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(),
code_snippet: r#"// Full identation blocks
// Spaces Spaces Spaces
// Tab Tab Tab
// Spaces Tab Spaces
// Tab Spaces Tab

// Partial identation blocks
// Space Tab
// Space Space Tab
// Space Space Space Tab
// Space / / Space
// Space Space / /
// Space Space Space /

// Use the configs above to play with the tab management
// Also existing tabs are kept as tabs.

fn main() {
println!("Hello world!");
}
"#.to_owned(),
}
}
}
Expand Down Expand Up @@ -144,7 +171,26 @@ impl Widgets {
ui.memory().id_data.insert(show_password_id, show_password);
});

ui.separator();

ui.label("Multiline text input:");
ui.text_edit_multiline(&mut self.multiline_text_input);

ui.separator();

ui.horizontal(|ui| {
ui.label("Code editor:");

ui.separator();

ui.checkbox(&mut self.tab_moves_focus, "Tabs moves focus");
});

ui.code_editor_with_config(
&mut self.code_snippet,
CodingConfig {
tab_moves_focus: self.tab_moves_focus,
},
);
}
}
3 changes: 2 additions & 1 deletion epaint/src/text/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl FontImpl {

if c == '\t' {
if let Some(space) = self.glyph_info(' ') {
glyph_info.advance_width = 4.0 * space.advance_width;
glyph_info.advance_width = crate::text::MAX_TAB_SIZE * space.advance_width;
}
}

Expand Down Expand Up @@ -285,6 +285,7 @@ impl Font {
for c in text.chars() {
if !self.fonts.is_empty() {
let (font_index, glyph_info) = self.glyph_info(c);

let font_impl = &self.fonts[font_index];

if let Some(last_glyph_id) = last_glyph_id {
Expand Down
3 changes: 3 additions & 0 deletions epaint/src/text/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ mod font;
mod fonts;
mod galley;

/// Default size for a `\t` character.
pub const MAX_TAB_SIZE: f32 = 4.0;

pub use {
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
galley::{Galley, Row},
Expand Down