Skip to content
Open
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
183 changes: 111 additions & 72 deletions crates/edit/src/bin/edit/draw_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,10 @@ use crate::localization::*;
use crate::state::*;

pub fn draw_editor(ctx: &mut Context, state: &mut State) {
if !matches!(state.wants_search.kind, StateSearchKind::Hidden | StateSearchKind::Disabled) {
draw_search(ctx, state);
}

let size = ctx.size();
// TODO: The layout code should be able to just figure out the height on its own.
let height_reduction = match state.wants_search.kind {
StateSearchKind::Search => 4,
StateSearchKind::Replace => 5,
_ => 2,
};
// EN: Search and Replace are centered modals, so only the menu and status rows are reserved.
// 中文:搜尋與取代改為中央對話框,因此只需保留選單列與狀態列。
let height_reduction = 2;

if let Some(doc) = state.documents.active() {
ctx.textarea("textarea", doc.buffer.clone());
Expand All @@ -35,7 +28,7 @@ pub fn draw_editor(ctx: &mut Context, state: &mut State) {
ctx.attr_intrinsic_size(Size { width: 0, height: size.height - height_reduction });
}

fn draw_search(ctx: &mut Context, state: &mut State) {
pub fn draw_dialog_search(ctx: &mut Context, state: &mut State) {
if let Err(err) = icu::init() {
error_log_add(ctx, state, err.into());
state.wants_search.kind = StateSearchKind::Disabled;
Expand All @@ -47,6 +40,7 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
return;
};

let kind = state.wants_search.kind;
let mut action = None;
let mut focus = StateSearchKind::Hidden;

Expand All @@ -58,22 +52,33 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
// Otherwise, focus the replace input field, if it exists.
if let Some(selection) = doc.buffer.borrow_mut().extract_user_selection(false) {
state.search_needle = string_from_utf8_lossy_owned(selection);
focus = state.wants_search.kind;
focus = kind;
}
}

ctx.block_begin("search");
ctx.attr_focus_well();
ctx.attr_background_rgba(ctx.indexed(IndexedColor::White));
ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::Black));
{
if ctx.contains_focus() && ctx.consume_shortcut(vk::ESCAPE) {
state.wants_search.kind = StateSearchKind::Hidden;
}
// EN: Render Search and Replace above the complete editor instead of consuming document rows.
// 中文:搜尋與取代顯示於完整編輯器上方,不再占用文件列。
let title = if kind == StateSearchKind::Replace {
loc(LocId::EditReplace)
} else {
loc(LocId::EditFind)
};
let mut cancel = false;

ctx.table_begin("needle");
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
ctx.modal_begin("search", title);
{
ctx.block_begin("search-content");
ctx.inherit_focus();
ctx.attr_padding(Rect::three(1, 2, 1));
{
// EN: F3 continues the search while the modal has focus.
// 中文:對話框取得焦點時,F3 仍可繼續尋找。
if ctx.contains_focus() && ctx.consume_shortcut(vk::F3) {
action = Some(SearchAction::Search);
}

ctx.table_begin("search-fields");
ctx.table_set_cell_gap(Size { width: 1, height: 0 });
{
ctx.table_next_row();
ctx.label("label", loc(LocId::SearchNeedleLabel));
Expand All @@ -85,75 +90,109 @@ fn draw_search(ctx: &mut Context, state: &mut State) {
ctx.attr_background_rgba(ctx.indexed(IndexedColor::Red));
ctx.attr_foreground_rgba(ctx.indexed(IndexedColor::BrightWhite));
}
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
ctx.attr_intrinsic_size(Size { width: 48, height: 1 });
if focus == StateSearchKind::Search {
ctx.steal_focus();
}
if ctx.is_focused() && ctx.consume_shortcut(vk::RETURN) {
action = Some(SearchAction::Search);
}
if kind == StateSearchKind::Replace {
ctx.table_next_row();
ctx.label("label", loc(LocId::SearchReplacementLabel));

ctx.editline("replacement", &mut state.search_replacement);
ctx.attr_intrinsic_size(Size { width: 48, height: 1 });
if focus == StateSearchKind::Replace {
ctx.steal_focus();
}
if ctx.is_focused() {
if ctx.consume_shortcut(vk::RETURN) {
action = Some(SearchAction::Replace);
} else if ctx.consume_shortcut(kbmod::CTRL_ALT | vk::RETURN) {
action = Some(SearchAction::ReplaceAll);
}
}
}
}
ctx.table_end();

ctx.table_begin("search-options");
ctx.attr_padding(Rect::three(1, 0, 0));
ctx.table_set_columns(&[48]);
{
let mut change = false;

if state.wants_search.kind == StateSearchKind::Replace {
// EN: Each translated option gets its own row so it cannot widen the modal.
// 中文:每個翻譯後的選項各占一列,避免撐寬對話框。
ctx.table_next_row();
change |= ctx.checkbox(
"match-case",
loc(LocId::SearchMatchCase),
&mut state.search_options.match_case,
);
ctx.table_next_row();
change |= ctx.checkbox(
"whole-word",
loc(LocId::SearchWholeWord),
&mut state.search_options.whole_word,
);
ctx.table_next_row();
ctx.label("label", loc(LocId::SearchReplacementLabel));
change |= ctx.checkbox(
"use-regex",
loc(LocId::SearchUseRegex),
&mut state.search_options.use_regex,
);
if change {
action = Some(SearchAction::Search);
}
}
ctx.table_end();

ctx.editline("replacement", &mut state.search_replacement);
ctx.attr_intrinsic_size(Size { width: COORD_TYPE_SAFE_MAX, height: 1 });
if focus == StateSearchKind::Replace {
ctx.steal_focus();
// EN: Explicit buttons keep all operations discoverable in the modal.
// 中文:以明確按鈕呈現對話框中的所有操作。
ctx.table_begin("search-actions");
ctx.inherit_focus();
ctx.attr_padding(Rect::three(1, 0, 0));
ctx.attr_position(Position::Center);
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
{
ctx.table_next_row();
ctx.inherit_focus();
if ctx.button("find", loc(LocId::EditFind), ButtonStyle::default()) {
action = Some(SearchAction::Search);
}
if ctx.is_focused() {
if ctx.consume_shortcut(vk::RETURN) {

if kind == StateSearchKind::Replace {
ctx.inherit_focus();
if ctx.button("replace", loc(LocId::EditReplace), ButtonStyle::default()) {
action = Some(SearchAction::Replace);
} else if ctx.consume_shortcut(kbmod::CTRL_ALT | vk::RETURN) {
}

ctx.inherit_focus();
if ctx.button(
"replace-all",
loc(LocId::SearchReplaceAll),
ButtonStyle::default(),
) {
action = Some(SearchAction::ReplaceAll);
}
}
}
}
ctx.table_end();

ctx.table_begin("options");
ctx.table_set_cell_gap(Size { width: 2, height: 0 });
{
let mut change = false;
let mut change_action = Some(SearchAction::Search);

ctx.table_next_row();

change |= ctx.checkbox(
"match-case",
loc(LocId::SearchMatchCase),
&mut state.search_options.match_case,
);
change |= ctx.checkbox(
"whole-word",
loc(LocId::SearchWholeWord),
&mut state.search_options.whole_word,
);
change |= ctx.checkbox(
"use-regex",
loc(LocId::SearchUseRegex),
&mut state.search_options.use_regex,
);
if state.wants_search.kind == StateSearchKind::Replace
&& ctx.button("replace-all", loc(LocId::SearchReplaceAll), ButtonStyle::default())
{
change = true;
change_action = Some(SearchAction::ReplaceAll);
}
if ctx.button("close", loc(LocId::SearchClose), ButtonStyle::default()) {
state.wants_search.kind = StateSearchKind::Hidden;
}

if change {
action = change_action;
ctx.inherit_focus();
cancel |= ctx.button("close", loc(LocId::SearchClose), ButtonStyle::default());
}
ctx.table_end();
}
ctx.table_end();
ctx.block_end();
}
cancel |= ctx.modal_end();

if cancel {
state.wants_search.kind = StateSearchKind::Hidden;
ctx.needs_rerender();
return;
}
ctx.block_end();

if let Some(action) = action {
search_execute(ctx, state, action);
Expand Down
6 changes: 6 additions & 0 deletions crates/edit/src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,12 @@ fn draw(ctx: &mut Context, state: &mut State) {
draw_editor(ctx, state);
draw_statusbar(ctx, state);

// EN: Draw Search and Replace after the editor so they appear as centered modal dialogs.
// 中文:在編輯器之後繪製搜尋與取代,使其顯示為中央對話框。
if !matches!(state.wants_search.kind, StateSearchKind::Hidden | StateSearchKind::Disabled) {
draw_dialog_search(ctx, state);
}

if state.wants_close {
draw_handle_wants_close(ctx, state);
}
Expand Down