Compare commits

...

5 Commits

Author SHA1 Message Date
ade1e60eb5
editor: Fix creation of new files
Some checks are pending
Build and test / build (push) Waiting to run
The editor is supposed to create files if they don't exist, however before this commit stat() would fail and exit load_file() before we even got to File::open_or_create().
2024-03-29 20:41:16 +01:00
662a315597
editor: Display only the basename of the current file in the window title 2024-03-29 20:36:44 +01:00
6c8a4ad995
terminal: Use widget->window() instead of the App's main window 2024-03-29 20:18:59 +01:00
8db14d8883
editor: Use TextInput as a base class 2024-03-29 20:18:58 +01:00
fbc91bfe8c
libui: Add a TextInput base class to handle most input fields and add an InputField class for single-line inputs 2024-03-29 20:18:58 +01:00
9 changed files with 269 additions and 50 deletions

View File

@ -9,23 +9,23 @@
#include "EditorWidget.h"
#include <ctype.h>
#include <luna/PathParser.h>
#include <luna/Utf8.h>
#include <os/File.h>
#include <os/FileSystem.h>
#include <ui/App.h>
EditorWidget::EditorWidget(SharedPtr<ui::Font> font) : ui::Widget(), m_font(font)
EditorWidget::EditorWidget(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
{
m_cursor_timer = os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }).release_value();
recalculate_lines();
}
Result<void> EditorWidget::load_file(const os::Path& path)
{
struct stat st;
TRY(os::FileSystem::stat(path, st, true));
auto rc = os::FileSystem::stat(path, st, true);
if (!S_ISREG(st.st_mode))
if (!rc.has_error() && !S_ISREG(st.st_mode))
{
os::eprintln("editor: not loading %s as it is not a regular file", path.name().chars());
return {};
@ -43,8 +43,10 @@ Result<void> EditorWidget::load_file(const os::Path& path)
m_path = path;
String title = TRY(String::format("Text Editor - %s"_sv, m_path.name().chars()));
ui::App::the().main_window()->set_title(title.view());
auto basename = TRY(PathParser::basename(m_path.name()));
String title = TRY(String::format("Text Editor - %s"_sv, basename.chars()));
window()->set_title(title.view());
TRY(recalculate_lines());
@ -62,8 +64,7 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_index();
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -73,8 +74,7 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_index();
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -84,8 +84,7 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_position();
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -95,8 +94,7 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
else
return ui::EventResult::DidNotHandle;
recalculate_cursor_position();
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -123,15 +121,11 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
m_cursor--;
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size));
memmove(slice, slice + 1, size - 1);
TRY(m_data.try_resize(m_data.size() - 1));
delete_current_character();
TRY(recalculate_lines());
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -140,18 +134,12 @@ Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
else
{
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size + 1));
memmove(slice + 1, slice, size);
*slice = request.letter;
}
TRY(insert_character(request.letter));
m_cursor++;
TRY(recalculate_lines());
m_cursor_timer->restart();
m_cursor_activated = true;
update_cursor();
return ui::EventResult::DidHandle;
}
@ -234,13 +222,6 @@ Result<void> EditorWidget::recalculate_lines()
return {};
}
void EditorWidget::tick_cursor()
{
m_cursor_activated = !m_cursor_activated;
ui::App::the().main_window()->draw();
}
void EditorWidget::recalculate_cursor_position()
{
if (m_cursor == 0) m_cursor_position = { 0, 0 };

View File

@ -10,9 +10,10 @@
#include <luna/String.h>
#include <os/Timer.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
#include <ui/Widget.h>
class EditorWidget : public ui::Widget
class EditorWidget : public ui::TextInput
{
public:
EditorWidget(SharedPtr<ui::Font> font);
@ -28,8 +29,6 @@ class EditorWidget : public ui::Widget
private:
SharedPtr<ui::Font> m_font;
Buffer m_data;
struct Line
{
usize begin;
@ -37,16 +36,8 @@ class EditorWidget : public ui::Widget
};
Vector<Line> m_lines;
usize m_cursor { 0 };
ui::Point m_cursor_position { 0, 0 };
OwnedPtr<os::Timer> m_cursor_timer;
bool m_cursor_activated = true;
os::Path m_path { AT_FDCWD };
void tick_cursor();
Result<void> recalculate_lines();
void recalculate_cursor_position();
void recalculate_cursor_index();

View File

@ -30,8 +30,8 @@ Result<int> luna_main(int argc, char** argv)
app.set_main_window(window);
auto* editor = TRY(make<EditorWidget>(ui::Font::default_font()));
if (!path.is_empty()) TRY(editor->load_file(path));
window->set_main_widget(*editor);
if (!path.is_empty()) TRY(editor->load_file(path));
window->draw();

View File

@ -17,6 +17,8 @@ set(SOURCES
src/Container.cpp
src/Button.cpp
src/Label.cpp
src/InputField.cpp
src/TextInput.cpp
)
add_library(ui ${SOURCES})

View File

@ -0,0 +1,40 @@
/**
* @file InputField.h
* @author apio (cloudapio.eu)
* @brief Single line text input widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#pragma once
#include <os/Action.h>
#include <ui/Font.h>
#include <ui/TextInput.h>
namespace ui
{
class InputField : public ui::TextInput
{
public:
InputField(SharedPtr<ui::Font> font);
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
Result<void> draw(ui::Canvas& canvas) override;
StringView data();
void on_submit(os::Function<StringView>&& action)
{
m_on_submit_action = move(action);
m_has_on_submit_action = true;
}
private:
SharedPtr<ui::Font> m_font;
os::Function<StringView> m_on_submit_action;
bool m_has_on_submit_action { false };
};
}

View File

@ -0,0 +1,41 @@
/**
* @file TextInput.h
* @author apio (cloudapio.eu)
* @brief Base class for text inputs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#pragma once
#include <luna/Buffer.h>
#include <os/Timer.h>
#include <ui/Widget.h>
namespace ui
{
class TextInput : public Widget
{
public:
TextInput();
virtual Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) = 0;
virtual Result<void> draw(ui::Canvas& canvas) = 0;
protected:
Buffer m_data;
usize m_cursor { 0 };
ui::Point m_cursor_position { 0, 0 };
OwnedPtr<os::Timer> m_cursor_timer;
bool m_cursor_activated = true;
void tick_cursor();
void update_cursor();
Result<void> delete_current_character();
Result<void> insert_character(char c);
};
}

112
libui/src/InputField.cpp Normal file
View File

@ -0,0 +1,112 @@
/**
* @file InputField.cpp
* @author apio (cloudapio.eu)
* @brief Single line text input widget.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <ctype.h>
#include <luna/String.h>
#include <luna/StringView.h>
#include <luna/Utf8.h>
#include <ui/InputField.h>
namespace ui
{
InputField::InputField(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
{
}
Result<ui::EventResult> InputField::handle_key_event(const ui::KeyEventRequest& request)
{
// Avoid handling "key released" events
if (!request.pressed) return ui::EventResult::DidNotHandle;
if (request.code == moon::K_LeftArrow)
{
if (m_cursor > 0) m_cursor--;
else
return ui::EventResult::DidNotHandle;
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_RightArrow)
{
if (m_cursor < m_data.size()) m_cursor++;
else
return ui::EventResult::DidNotHandle;
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.code == moon::K_Backspace)
{
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
m_cursor--;
delete_current_character();
update_cursor();
return ui::EventResult::DidHandle;
}
if (request.letter == '\n')
{
if (m_has_on_submit_action)
{
m_on_submit_action(data());
return ui::EventResult::DidHandle;
}
return ui::EventResult::DidNotHandle;
}
if (iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
else
TRY(insert_character(request.letter));
m_cursor++;
update_cursor();
return ui::EventResult::DidHandle;
}
Result<void> InputField::draw(ui::Canvas& canvas)
{
int visible_characters = canvas.width / m_font->width();
auto string = data();
Utf8StringDecoder decoder(string.chars());
wchar_t buf[4096];
decoder.decode(buf, sizeof(buf)).release_value();
int characters_to_render = (int)wcslen(buf);
for (int j = 0; j < visible_characters && j < characters_to_render; j++)
{
auto subcanvas = canvas.subcanvas({ j * m_font->width(), 0, m_font->width(), m_font->height() });
m_font->render(buf[j], ui::WHITE, subcanvas);
}
// Draw the cursor
if ((int)m_cursor < visible_characters && m_cursor_activated)
{
canvas.subcanvas({ (int)m_cursor * m_font->width(), 0, 1, m_font->height() }).fill(ui::WHITE);
}
return {};
}
StringView InputField::data()
{
return StringView { (const char*)m_data.data(), m_data.size() };
}
}

52
libui/src/TextInput.cpp Normal file
View File

@ -0,0 +1,52 @@
/**
* @file TextInput.cpp
* @author apio (cloudapio.eu)
* @brief Base class for text inputs.
*
* @copyright Copyright (c) 2024, the Luna authors.
*
*/
#include <ui/App.h>
#include <ui/TextInput.h>
namespace ui
{
TextInput::TextInput() : Widget()
{
m_cursor_timer = os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }).release_value();
}
void TextInput::update_cursor()
{
m_cursor_timer->restart();
m_cursor_activated = true;
}
Result<void> TextInput::delete_current_character()
{
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size));
memmove(slice, slice + 1, size - 1);
TRY(m_data.try_resize(m_data.size() - 1));
return {};
}
Result<void> TextInput::insert_character(char c)
{
usize size = m_data.size() - m_cursor;
u8* slice = TRY(m_data.slice(m_cursor, size + 1));
memmove(slice + 1, slice, size);
*slice = (u8)c;
return {};
}
void TextInput::tick_cursor()
{
m_cursor_activated = !m_cursor_activated;
window()->draw();
}
}

View File

@ -40,7 +40,7 @@ Result<void> TerminalWidget::init(char* const* args)
m_font = ui::Font::default_font();
m_bold_font = ui::Font::default_bold_font();
m_terminal_canvas = ui::App::the().main_window()->canvas();
m_terminal_canvas = window()->canvas();
m_terminal_canvas.fill(ui::BLACK);
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
@ -102,7 +102,7 @@ Result<void> TerminalWidget::process()
if (did_draw) drawn++;
}
if (drawn > 0) ui::App::the().main_window()->draw();
if (drawn > 0) window()->draw();
return {};
}
@ -117,7 +117,7 @@ void TerminalWidget::tick_cursor()
else
erase_current_char();
ui::App::the().main_window()->draw();
window()->draw();
}
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)