Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions src/bin/edit/draw_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
}

if state.wants_search.kind == StateSearchKind::Replace
&& ctx.button("replace-all", loc(LocId::SearchReplaceAll))
&& ctx.button("replace-all", loc(LocId::SearchReplaceAll), ButtonStyle::default())
{
action = SearchAction::ReplaceAll;
}

if ctx.button("close", loc(LocId::SearchClose)) {
if ctx.button("close", loc(LocId::SearchClose), ButtonStyle::default()) {
state.wants_search.kind = StateSearchKind::Hidden;
}
}
Expand Down Expand Up @@ -239,18 +239,18 @@ pub fn draw_handle_wants_close(ctx: &mut Context, state: &mut State) {
ctx.table_next_row();
ctx.inherit_focus();

if ctx.button("yes", loc(LocId::UnsavedChangesDialogYes)) {
if ctx.button("yes", loc(LocId::UnsavedChangesDialogYes), ButtonStyle::default().accelerator('S')) {
action = Action::Save;
}
ctx.inherit_focus();
if ctx.button("no", loc(LocId::UnsavedChangesDialogNo)) {
if ctx.button("no", loc(LocId::UnsavedChangesDialogNo), ButtonStyle::default().accelerator('N')) {
action = Action::Discard;
}
if ctx.button("cancel", loc(LocId::Cancel)) {
if ctx.button("cancel", loc(LocId::Cancel), ButtonStyle::default()) {
action = Action::Cancel;
}

// TODO: This should highlight the corresponding letter in the label.
// Handle accelerator shortcuts
if ctx.consume_shortcut(vk::S) {
action = Action::Save;
} else if ctx.consume_shortcut(vk::N) {
Expand Down
4 changes: 2 additions & 2 deletions src/bin/edit/draw_filepicker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ pub fn draw_file_picker(ctx: &mut Context, state: &mut State) {
ctx.table_next_row();
ctx.inherit_focus();

save = ctx.button("yes", loc(LocId::Yes));
save = ctx.button("yes", loc(LocId::Yes), ButtonStyle::default());
ctx.inherit_focus();

if ctx.button("no", loc(LocId::No)) {
if ctx.button("no", loc(LocId::No), ButtonStyle::default()) {
state.file_picker_overwrite_warning = None;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/bin/edit/draw_menubar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ pub fn draw_dialog_about(ctx: &mut Context, state: &mut State) {
ctx.attr_padding(Rect::three(1, 2, 0));
ctx.attr_position(Position::Center);
{
if ctx.button("ok", loc(LocId::Ok)) {
if ctx.button("ok", loc(LocId::Ok), ButtonStyle::default()) {
state.wants_about = false;
}
ctx.inherit_focus();
Expand Down
13 changes: 7 additions & 6 deletions src/bin/edit/draw_statusbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {

ctx.table_next_row();

if ctx.button("newline", if tb.is_crlf() { "CRLF" } else { "LF" }) {
if ctx.button("newline", if tb.is_crlf() { "CRLF" } else { "LF" }, ButtonStyle::default()) {
let is_crlf = tb.is_crlf();
tb.normalize_newlines(!is_crlf);
}
Expand All @@ -33,7 +33,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
ctx.steal_focus();
}

state.wants_encoding_picker |= ctx.button("encoding", tb.encoding());
state.wants_encoding_picker |= ctx.button("encoding", tb.encoding(), ButtonStyle::default());
if state.wants_encoding_picker {
if doc.path.is_some() {
ctx.block_begin("frame");
Expand All @@ -47,11 +47,11 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
ctx.attr_padding(Rect::two(0, 1));
ctx.attr_border();
{
if ctx.button("reopen", loc(LocId::EncodingReopen)) {
if ctx.button("reopen", loc(LocId::EncodingReopen), ButtonStyle::default()) {
state.wants_encoding_change = StateEncodingChange::Reopen;
}
ctx.focus_on_first_present();
if ctx.button("convert", loc(LocId::EncodingConvert)) {
if ctx.button("convert", loc(LocId::EncodingConvert), ButtonStyle::default()) {
state.wants_encoding_change = StateEncodingChange::Convert;
}
}
Expand Down Expand Up @@ -79,6 +79,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
}),
tb.tab_size(),
),
ButtonStyle::default()
);
if state.wants_indentation_picker {
ctx.table_begin("indentation-picker");
Expand Down Expand Up @@ -159,7 +160,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
&arena_format!(ctx.arena(), "{}/{}", tb.logical_line_count(), tb.visual_line_count(),),
);

if tb.is_overtype() && ctx.button("overtype", "OVR") {
if tb.is_overtype() && ctx.button("overtype", "OVR", ButtonStyle::default()) {
tb.set_overtype(false);
ctx.needs_rerender();
}
Expand All @@ -180,7 +181,7 @@ pub fn draw_statusbar(ctx: &mut Context, state: &mut State) {
filename = &filename_buf;
}

state.wants_document_picker |= ctx.button("filename", filename);
state.wants_document_picker |= ctx.button("filename", filename, ButtonStyle::default());
ctx.inherit_focus();
ctx.attr_overflow(Overflow::TruncateMiddle);
ctx.attr_position(Position::Right);
Expand Down
8 changes: 4 additions & 4 deletions src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,26 +452,26 @@ fn draw_handle_clipboard_change(ctx: &mut Context, state: &mut State) {
ctx.inherit_focus();

if over_limit {
if ctx.button("ok", loc(LocId::Ok)) {
if ctx.button("ok", loc(LocId::Ok), ButtonStyle::default()) {
state.osc_clipboard_seen_generation = generation;
}
ctx.inherit_focus();
} else {
if ctx.button("always", loc(LocId::Always)) {
if ctx.button("always", loc(LocId::Always), ButtonStyle::default()) {
state.osc_clipboard_always_send = true;
state.osc_clipboard_seen_generation = generation;
state.osc_clipboard_send_generation = generation;
}

if ctx.button("yes", loc(LocId::Yes)) {
if ctx.button("yes", loc(LocId::Yes), ButtonStyle::default()) {
state.osc_clipboard_seen_generation = generation;
state.osc_clipboard_send_generation = generation;
}
if ctx.clipboard().len() < 10 * LARGE_CLIPBOARD_THRESHOLD {
ctx.inherit_focus();
}

if ctx.button("no", loc(LocId::No)) {
if ctx.button("no", loc(LocId::No), ButtonStyle::default()) {
state.osc_clipboard_seen_generation = generation;
}
if ctx.clipboard().len() >= 10 * LARGE_CLIPBOARD_THRESHOLD {
Expand Down
2 changes: 1 addition & 1 deletion src/bin/edit/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ pub fn draw_error_log(ctx: &mut Context, state: &mut State) {
}
ctx.block_end();

if ctx.button("ok", loc(LocId::Ok)) {
if ctx.button("ok", loc(LocId::Ok), ButtonStyle::default()) {
state.error_log_count = 0;
}
ctx.attr_position(Position::Center);
Expand Down
143 changes: 93 additions & 50 deletions src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,41 @@ pub enum Overflow {
TruncateTail,
}

/// Controls the style with which a button label renders
#[derive(Clone, Copy)]
pub struct ButtonStyle {
accelerator: Option<char>,
checked: Option<bool>,
bracketed: bool,
}
Comment on lines +261 to +266
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: In the future we'll likely have to remove this again. The long-term plan is to add an extension API, including a way to expose the UI framework (so that extensions can draw their own UI). No matter what type of extensions we build, it's unlikely that we can just expose a builder pattern as-is.

However, I think those concerns are still in the distant future so not worth freaking out about just yet. But I figured you may find it curious if you want to ever contribute other parts, or if you simply wondered why the current UI framework is so verbose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be replaced with a basic struct e.g. ButtonStyle { checked: None, accelerator: None, bracketed: true }, which might make it easier to map through an FFI boundary?

Or perhaps buttons end up with their own "NodeContent" enumeration, so you can set attributes over a number of calls and leave the drawing to the render phase.


impl ButtonStyle {
/// Draw an accelerator label: `[_E_xample button]` or `[Example button(X)]`
///
/// Must provide an upper-case ASCII character.
pub fn accelerator(self, char: char) -> Self {
Self { accelerator: Some(char), ..self }
}
/// Draw a checkbox prefix: `[▣ Example Button]`
pub fn checked(self, checked: bool) -> Self {
Self { checked: Some(checked), ..self }
}
/// Draw with or without brackets: `[Example Button]` or `Example Button`
pub fn bracketed(self, bracketed: bool) -> Self {
Self { bracketed, ..self }
}
}

impl Default for ButtonStyle {
fn default() -> Self {
Self {
accelerator: None,
checked: None,
bracketed: true, // Default style for most buttons. Brackets may be disabled e.g. for buttons in menus
}
}
}

/// There's two types of lifetimes the TUI code needs to manage:
/// * Across frames
/// * Per frame
Expand Down Expand Up @@ -652,7 +687,7 @@ impl Tui {
fn report_context_completion<'a>(&'a mut self, ctx: &mut Context<'a, '_>) {
// If this hits, you forgot to block_end() somewhere. The best way to figure
// out where is to do a binary search of commenting out code in main.rs.
debug_assert!(ctx.tree.current_node.borrow().stack_parent.is_none());
debug_assert!(ctx.tree.current_node.borrow().stack_parent.is_none(), "Dangling parent! Did you miss a block_end?");

// End the root node.
ctx.block_end();
Expand Down Expand Up @@ -1944,17 +1979,12 @@ impl<'a> Context<'a, '_> {

/// Creates a button with the given text.
/// Returns true if the button was activated.
pub fn button(&mut self, classname: &'static str, text: &str) -> bool {
self.styled_label_begin(classname);
pub fn button(&mut self, classname: &'static str, text: &str, style: ButtonStyle) -> bool {
self.button_label(classname, text, style);
self.attr_focusable();
if self.is_focused() {
self.attr_reverse();
}
self.styled_label_add_text("[");
self.styled_label_add_text(text);
self.styled_label_add_text("]");
self.styled_label_end();

self.button_activated()
}

Expand Down Expand Up @@ -3024,7 +3054,7 @@ impl<'a> Context<'a, '_> {
let mixin = self.tree.current_node.borrow().child_count as u64;
self.next_block_id_mixin(mixin);

self.menubar_label(text, accelerator, None);
self.button_label("menu_button", text, ButtonStyle::default().accelerator(accelerator).bracketed(false));
self.attr_focusable();
self.attr_padding(Rect::two(0, 1));

Expand Down Expand Up @@ -3098,7 +3128,7 @@ impl<'a> Context<'a, '_> {
let clicked =
self.button_activated() || self.consume_shortcut(InputKey::new(accelerator as u32));

self.menubar_label(text, accelerator, Some(checked));
self.button_label("menu_checkbox", text, ButtonStyle::default().bracketed(false).checked(checked).accelerator(accelerator));
self.menubar_shortcut(shortcut);

if clicked {
Expand Down Expand Up @@ -3146,51 +3176,64 @@ impl<'a> Context<'a, '_> {
self.table_end();
}

fn menubar_label(&mut self, text: &str, accelerator: char, checked: Option<bool>) {
if !accelerator.is_ascii_uppercase() {
self.label("label", text);
return;
}

let mut off = text.len();

for (i, c) in text.bytes().enumerate() {
// Perfect match (uppercase character) --> stop
if c as char == accelerator {
off = i;
break;
}
// Inexact match (lowercase character) --> use first hit
if (c & !0x20) as char == accelerator && off == text.len() {
off = i;
}
/// Renders a button label with an optional accelerator character
/// May also renders a checkbox or square brackets for inline buttons
fn button_label(&mut self, classname: &'static str, text: &str, style: ButtonStyle) {
// Label prefix
self.styled_label_begin(classname);
if style.bracketed {
self.styled_label_add_text("[");
}

self.styled_label_begin("label");
if let Some(checked) = checked {
if let Some(checked) = style.checked {
self.styled_label_add_text(if checked { "▣ " } else { " " });
}
// Label text
match style.accelerator {
Some(accelerator) if accelerator.is_ascii_uppercase() => {
// Complex case:
// Locate the offset of the acclerator character in the label text
let mut off = text.len();
for (i, c) in text.bytes().enumerate() {
// Perfect match (uppercase character) --> stop
if c as char == accelerator {
off = i;
break;
}
// Inexact match (lowercase character) --> use first hit
if (c & !0x20) as char == accelerator && off == text.len() {
off = i;
}
}

if off < text.len() {
// Add an underline to the accelerator.
self.styled_label_add_text(&text[..off]);
self.styled_label_set_attributes(Attributes::Underlined);
self.styled_label_add_text(&text[off..off + 1]);
self.styled_label_set_attributes(Attributes::None);
self.styled_label_add_text(&text[off + 1..]);
} else {
// Add the accelerator in parentheses and underline it.
let ch = accelerator as u8;
self.styled_label_add_text(text);
self.styled_label_add_text("(");
self.styled_label_set_attributes(Attributes::Underlined);
self.styled_label_add_text(unsafe { str_from_raw_parts(&ch, 1) });
self.styled_label_set_attributes(Attributes::None);
self.styled_label_add_text(")");
if off < text.len() {
// Add an underline to the accelerator.
self.styled_label_add_text(&text[..off]);
self.styled_label_set_attributes(Attributes::Underlined);
self.styled_label_add_text(&text[off..off + 1]);
self.styled_label_set_attributes(Attributes::None);
self.styled_label_add_text(&text[off + 1..]);
} else {
// Add the accelerator in parentheses and underline it.
let ch = accelerator as u8;
self.styled_label_add_text(text);
self.styled_label_add_text("(");
self.styled_label_set_attributes(Attributes::Underlined);
self.styled_label_add_text(unsafe { str_from_raw_parts(&ch, 1) });
self.styled_label_set_attributes(Attributes::None);
self.styled_label_add_text(")");
}
},
_ => {
// Simple case:
// no accelerator character
self.styled_label_add_text(text);
},
}
// Label postfix
if style.bracketed {
self.styled_label_add_text("]");
}

self.styled_label_end();
self.attr_padding(Rect { left: 0, top: 0, right: 2, bottom: 0 });
}

fn menubar_shortcut(&mut self, shortcut: InputKey) {
Expand All @@ -3216,7 +3259,7 @@ impl<'a> Context<'a, '_> {
self.block_begin("shortcut");
self.block_end();
}
self.attr_padding(Rect { left: 0, top: 0, right: 2, bottom: 0 });
self.attr_padding(Rect { left: 2, top: 0, right: 2, bottom: 0 });
}
}

Expand Down