mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-06-24 06:44:25 -04:00
Add edit support for Notepad (#1093)
* WIP file editing * WIP file editing * Add "on_pop" handler to navigation. * WIP Editing * WIP for draft * Fix mock and unit tests, support +newline at end. * Clean up Painter API and use string_view * Fix optional rvalue functions * Fix Result 'take' to be more standard * FileWrapper stack buffer reads * Grasping at straws * Nit * Move set_on_pop impl to cpp * Workaround "Open" when file not dirty. --------- Co-authored-by: kallanreed <kallanreed@outlook.com>
This commit is contained in:
parent
69011754c9
commit
8d7fdeb633
11 changed files with 847 additions and 148 deletions
|
@ -124,6 +124,22 @@ bool TextViewer::on_encoder(EncoderEvent delta) {
|
|||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
uint32_t TextViewer::offset() const {
|
||||
auto range = file_->line_range(cursor_.line);
|
||||
if (range)
|
||||
return range->start + col();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
|
||||
bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_col) {
|
||||
if (!has_file())
|
||||
return false;
|
||||
|
@ -175,28 +191,24 @@ bool TextViewer::apply_scrolling_constraints(int16_t delta_line, int16_t delta_c
|
|||
return true;
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void TextViewer::paint_text(Painter& painter, uint32_t line, uint16_t col) {
|
||||
auto r = screen_rect();
|
||||
char buffer[max_col + 1];
|
||||
|
||||
// Draw the lines from the file
|
||||
for (auto i = 0u; i < max_line; ++i) {
|
||||
if (line + i >= file_->line_count())
|
||||
break;
|
||||
|
||||
auto str = file_->get_text(line + i, col, max_col);
|
||||
auto result = file_->get_text(line + i, col, buffer, max_col);
|
||||
|
||||
if (str && str->length() > 0)
|
||||
if (result && *result > 0)
|
||||
painter.draw_string(
|
||||
{0, r.top() + (int)i * char_height},
|
||||
style_text, *str);
|
||||
style_text, {buffer, *result});
|
||||
|
||||
// Clear empty line sections. This is less visually jarring than full clear.
|
||||
int32_t clear_width = max_col - (str ? str->length() : 0);
|
||||
int32_t clear_width = max_col - (result ? *result : 0);
|
||||
if (clear_width > 0)
|
||||
painter.fill_rectangle(
|
||||
{(max_col - clear_width) * char_width,
|
||||
|
@ -238,10 +250,6 @@ void TextViewer::reset_file(FileWrapper* file) {
|
|||
redraw(true);
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
|
||||
/* TextEditorMenu ***************************************************/
|
||||
|
||||
TextEditorMenu::TextEditorMenu()
|
||||
|
@ -309,25 +317,52 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
|||
menu.on_copy() = [this]() {
|
||||
show_nyi();
|
||||
};
|
||||
|
||||
menu.on_delete_line() = [this]() {
|
||||
show_nyi();
|
||||
prepare_for_write();
|
||||
file_->delete_line(viewer.line());
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_edit_line() = [this]() {
|
||||
show_nyi();
|
||||
show_edit_line();
|
||||
};
|
||||
|
||||
menu.on_add_line() = [this]() {
|
||||
show_nyi();
|
||||
prepare_for_write();
|
||||
|
||||
if (viewer.offset() < file_->size() - 1)
|
||||
file_->insert_line(viewer.line());
|
||||
else
|
||||
file_->insert_line(-1); // Add after last line.
|
||||
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_open() = [this]() {
|
||||
// TODO: confirm.
|
||||
show_file_picker();
|
||||
/*show_save_prompt([this]() {
|
||||
show_file_picker();
|
||||
});*/
|
||||
// HACK: above should work but it's faulting.
|
||||
if (!file_dirty_) {
|
||||
show_file_picker();
|
||||
} else {
|
||||
show_save_prompt(nullptr);
|
||||
show_file_picker(false);
|
||||
}
|
||||
};
|
||||
|
||||
menu.on_save() = [this]() {
|
||||
show_nyi();
|
||||
save_temp_file();
|
||||
hide_menu(true);
|
||||
};
|
||||
|
||||
menu.on_exit() = [this]() {
|
||||
// TODO: confirm.
|
||||
nav_.pop();
|
||||
show_save_prompt([this]() {
|
||||
nav_.pop();
|
||||
});
|
||||
};
|
||||
|
||||
button_menu.on_select = [this]() {
|
||||
|
@ -345,6 +380,10 @@ TextEditorView::TextEditorView(NavigationView& nav, const fs::path& path)
|
|||
open_file(path);
|
||||
}
|
||||
|
||||
TextEditorView::~TextEditorView() {
|
||||
delete_temp_file();
|
||||
}
|
||||
|
||||
void TextEditorView::on_show() {
|
||||
if (file_)
|
||||
viewer.focus();
|
||||
|
@ -353,14 +392,21 @@ void TextEditorView::on_show() {
|
|||
}
|
||||
|
||||
void TextEditorView::open_file(const fs::path& path) {
|
||||
file_.reset();
|
||||
viewer.clear_file();
|
||||
delete_temp_file();
|
||||
|
||||
path_ = {};
|
||||
file_dirty_ = false;
|
||||
has_temp_file_ = false;
|
||||
auto result = FileWrapper::open(path);
|
||||
|
||||
if (!result) {
|
||||
nav_.display_modal("Read Error", "Cannot open file:\n" + result.error().what());
|
||||
file_.reset();
|
||||
viewer.clear_file();
|
||||
|
||||
} else {
|
||||
file_ = result.take();
|
||||
file_ = *std::move(result);
|
||||
path_ = path;
|
||||
viewer.set_file(*file_);
|
||||
}
|
||||
|
||||
|
@ -401,16 +447,100 @@ void TextEditorView::hide_menu(bool hidden) {
|
|||
set_dirty();
|
||||
}
|
||||
|
||||
void TextEditorView::show_file_picker() {
|
||||
auto open_view = nav_.push<FileLoadView>("");
|
||||
open_view->on_changed = [this](std::filesystem::path path) {
|
||||
open_file(path);
|
||||
hide_menu();
|
||||
};
|
||||
void TextEditorView::show_file_picker(bool immediate) {
|
||||
// TODO: immediate is a hack until nav_.on_pop is fixed.
|
||||
auto open_view = immediate ? nav_.push<FileLoadView>("") : nav_.push_under_current<FileLoadView>("");
|
||||
|
||||
if (open_view) {
|
||||
open_view->on_changed = [this](std::filesystem::path path) {
|
||||
open_file(path);
|
||||
hide_menu();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditorView::show_edit_line() {
|
||||
auto str = file_->get_text(viewer.line(), 0, viewer.line_length());
|
||||
if (!str) {
|
||||
nav_.display_modal("Error", "Failed to get line text.");
|
||||
return;
|
||||
}
|
||||
|
||||
edit_line_buffer_ = *std::move(str);
|
||||
|
||||
text_prompt(
|
||||
nav_,
|
||||
edit_line_buffer_,
|
||||
viewer.col(),
|
||||
max_edit_length,
|
||||
[this](std::string& buffer) {
|
||||
auto range = file_->line_range(viewer.line());
|
||||
if (!range)
|
||||
return;
|
||||
|
||||
prepare_for_write();
|
||||
file_->replace_range(*range, buffer);
|
||||
});
|
||||
nav_.set_on_pop([this]() {
|
||||
edit_line_buffer_.clear();
|
||||
refresh_ui();
|
||||
hide_menu(true);
|
||||
});
|
||||
}
|
||||
|
||||
void TextEditorView::show_nyi() {
|
||||
nav_.display_modal("Soon...", "Coming soon.");
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
void TextEditorView::show_save_prompt(std::function<void()> continuation) {
|
||||
if (!file_dirty_) {
|
||||
if (continuation)
|
||||
continuation();
|
||||
return;
|
||||
}
|
||||
|
||||
nav_.display_modal(
|
||||
"Save?", "Save changes?", YESNO,
|
||||
[this](bool choice) {
|
||||
if (choice)
|
||||
save_temp_file();
|
||||
});
|
||||
nav_.set_on_pop(continuation);
|
||||
}
|
||||
|
||||
void TextEditorView::prepare_for_write() {
|
||||
file_dirty_ = true;
|
||||
|
||||
if (has_temp_file_)
|
||||
return;
|
||||
|
||||
// Copy to temp file on write.
|
||||
has_temp_file_ = true;
|
||||
delete_temp_file();
|
||||
copy_file(path_, get_temp_path());
|
||||
file_->assume_file(get_temp_path());
|
||||
}
|
||||
|
||||
void TextEditorView::delete_temp_file() const {
|
||||
auto temp_path = get_temp_path();
|
||||
if (!temp_path.empty()) {
|
||||
delete_file(temp_path);
|
||||
}
|
||||
}
|
||||
|
||||
void TextEditorView::save_temp_file() {
|
||||
if (file_dirty_) {
|
||||
delete_file(path_);
|
||||
copy_file(get_temp_path(), path_);
|
||||
file_dirty_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
fs::path TextEditorView::get_temp_path() const {
|
||||
if (!path_.empty())
|
||||
return path_ + "~";
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue