Compare commits
9 Commits
701dc30221
...
1176e64a7c
Author | SHA1 | Date | |
---|---|---|---|
1176e64a7c | |||
e7780b04ee | |||
6ded7247e0 | |||
8b3755873b | |||
489d54c531 | |||
eb3af60497 | |||
aee100753d | |||
0be6a896bb | |||
7d738433ed |
@ -53,3 +53,4 @@ add_subdirectory(tests)
|
|||||||
add_subdirectory(shell)
|
add_subdirectory(shell)
|
||||||
add_subdirectory(wind)
|
add_subdirectory(wind)
|
||||||
add_subdirectory(terminal)
|
add_subdirectory(terminal)
|
||||||
|
add_subdirectory(editor)
|
||||||
|
12
editor/CMakeLists.txt
Normal file
12
editor/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
|
EditorWidget.h
|
||||||
|
EditorWidget.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(editor ${SOURCES})
|
||||||
|
target_compile_options(editor PRIVATE -Os ${COMMON_FLAGS} -Wno-write-strings)
|
||||||
|
add_dependencies(editor libc)
|
||||||
|
target_include_directories(editor PRIVATE ${LUNA_BASE}/usr/include ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
target_link_libraries(editor PRIVATE os ui)
|
||||||
|
install(TARGETS editor DESTINATION ${LUNA_BASE}/usr/bin)
|
251
editor/EditorWidget.cpp
Normal file
251
editor/EditorWidget.cpp
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
/**
|
||||||
|
* @file EditorWidget.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Multiline text editing widget.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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::TextInput(), m_font(font)
|
||||||
|
{
|
||||||
|
recalculate_lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> EditorWidget::load_file(const os::Path& path)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
auto rc = os::FileSystem::stat(path, st, true);
|
||||||
|
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
os::eprintln("Loading file: %s", path.name().chars());
|
||||||
|
|
||||||
|
auto file = TRY(os::File::open_or_create(path, os::File::ReadOnly));
|
||||||
|
|
||||||
|
m_data = TRY(file->read_all());
|
||||||
|
|
||||||
|
os::eprintln("Read %zu bytes.", m_data.size());
|
||||||
|
|
||||||
|
m_cursor = m_data.size();
|
||||||
|
|
||||||
|
m_path = path;
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<ui::EventResult> EditorWidget::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
|
{
|
||||||
|
// Avoid handling "key released" events
|
||||||
|
if (!request.pressed) return ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
|
if (request.code == moon::K_UpArrow)
|
||||||
|
{
|
||||||
|
if (m_cursor_position.y > 0) m_cursor_position.y--;
|
||||||
|
else
|
||||||
|
return ui::EventResult::DidNotHandle;
|
||||||
|
recalculate_cursor_index();
|
||||||
|
update_cursor();
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.code == moon::K_DownArrow)
|
||||||
|
{
|
||||||
|
if (m_cursor_position.y + 1 < (int)m_lines.size()) m_cursor_position.y++;
|
||||||
|
else
|
||||||
|
return ui::EventResult::DidNotHandle;
|
||||||
|
recalculate_cursor_index();
|
||||||
|
update_cursor();
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.code == moon::K_LeftArrow)
|
||||||
|
{
|
||||||
|
if (m_cursor > 0) m_cursor--;
|
||||||
|
else
|
||||||
|
return ui::EventResult::DidNotHandle;
|
||||||
|
recalculate_cursor_position();
|
||||||
|
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;
|
||||||
|
recalculate_cursor_position();
|
||||||
|
update_cursor();
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.modifiers & ui::Mod_Ctrl)
|
||||||
|
{
|
||||||
|
switch (request.key)
|
||||||
|
{
|
||||||
|
case 's': {
|
||||||
|
auto result = save_file();
|
||||||
|
if (result.has_error())
|
||||||
|
{
|
||||||
|
os::eprintln("editor: failed to save file: %s", result.error_string());
|
||||||
|
return ui::EventResult::DidNotHandle;
|
||||||
|
}
|
||||||
|
os::println("editor: buffer saved to %s successfully", m_path.name().chars());
|
||||||
|
return ui::EventResult::DidNotHandle;
|
||||||
|
}
|
||||||
|
default: return ui::EventResult::DidNotHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.code == moon::K_Backspace)
|
||||||
|
{
|
||||||
|
if (m_cursor == 0) return ui::EventResult::DidNotHandle;
|
||||||
|
m_cursor--;
|
||||||
|
|
||||||
|
delete_current_character();
|
||||||
|
|
||||||
|
TRY(recalculate_lines());
|
||||||
|
|
||||||
|
update_cursor();
|
||||||
|
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.letter != '\n' && 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++;
|
||||||
|
TRY(recalculate_lines());
|
||||||
|
|
||||||
|
update_cursor();
|
||||||
|
|
||||||
|
return ui::EventResult::DidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> EditorWidget::save_file()
|
||||||
|
{
|
||||||
|
if (m_path.is_empty_path())
|
||||||
|
{
|
||||||
|
os::eprintln("editor: no file to save buffer to!");
|
||||||
|
return err(ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = TRY(os::File::open(m_path, os::File::WriteOnly));
|
||||||
|
return file->write(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> EditorWidget::draw(ui::Canvas& canvas)
|
||||||
|
{
|
||||||
|
int visible_lines = canvas.height / m_font->height();
|
||||||
|
int visible_columns = canvas.width / m_font->width();
|
||||||
|
|
||||||
|
if ((usize)visible_lines > m_lines.size()) visible_lines = static_cast<int>(m_lines.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < visible_lines; i++)
|
||||||
|
{
|
||||||
|
auto line = m_lines[i];
|
||||||
|
if (line.begin == line.end) continue;
|
||||||
|
|
||||||
|
auto slice = TRY(m_data.slice(line.begin, line.end - line.begin));
|
||||||
|
auto string = TRY(
|
||||||
|
String::from_string_view(StringView::from_fixed_size_cstring((const char*)slice, line.end - line.begin)));
|
||||||
|
|
||||||
|
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_columns && j < characters_to_render; j++)
|
||||||
|
{
|
||||||
|
auto subcanvas =
|
||||||
|
canvas.subcanvas({ j * m_font->width(), i * m_font->height(), m_font->width(), m_font->height() });
|
||||||
|
m_font->render(buf[j], ui::WHITE, subcanvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the cursor
|
||||||
|
if (m_cursor_position.x < visible_columns && m_cursor_position.y < visible_lines && m_cursor_activated)
|
||||||
|
{
|
||||||
|
canvas
|
||||||
|
.subcanvas(
|
||||||
|
{ m_cursor_position.x * m_font->width(), m_cursor_position.y * m_font->height(), 1, m_font->height() })
|
||||||
|
.fill(ui::WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<void> EditorWidget::recalculate_lines()
|
||||||
|
{
|
||||||
|
m_lines.clear();
|
||||||
|
|
||||||
|
Line l;
|
||||||
|
l.begin = 0;
|
||||||
|
for (usize i = 0; i < m_data.size(); i++)
|
||||||
|
{
|
||||||
|
if (m_data.data()[i] == '\n')
|
||||||
|
{
|
||||||
|
l.end = i;
|
||||||
|
TRY(m_lines.try_append(l));
|
||||||
|
l.begin = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.end = m_data.size();
|
||||||
|
TRY(m_lines.try_append(l));
|
||||||
|
|
||||||
|
recalculate_cursor_position();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorWidget::recalculate_cursor_position()
|
||||||
|
{
|
||||||
|
if (m_cursor == 0) m_cursor_position = { 0, 0 };
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)m_lines.size(); i++)
|
||||||
|
{
|
||||||
|
auto line = m_lines[i];
|
||||||
|
if (m_cursor >= line.begin && m_cursor <= line.end)
|
||||||
|
{
|
||||||
|
m_cursor_position.x = (int)(m_cursor - line.begin);
|
||||||
|
m_cursor_position.y = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorWidget::recalculate_cursor_index()
|
||||||
|
{
|
||||||
|
m_cursor = m_lines[m_cursor_position.y].begin + m_cursor_position.x;
|
||||||
|
if (m_cursor > m_lines[m_cursor_position.y].end)
|
||||||
|
{
|
||||||
|
m_cursor = m_lines[m_cursor_position.y].end;
|
||||||
|
recalculate_cursor_position();
|
||||||
|
}
|
||||||
|
}
|
44
editor/EditorWidget.h
Normal file
44
editor/EditorWidget.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @file EditorWidget.h
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Multiline text editing widget.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <luna/String.h>
|
||||||
|
#include <os/Timer.h>
|
||||||
|
#include <ui/Font.h>
|
||||||
|
#include <ui/TextInput.h>
|
||||||
|
#include <ui/Widget.h>
|
||||||
|
|
||||||
|
class EditorWidget : public ui::TextInput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EditorWidget(SharedPtr<ui::Font> font);
|
||||||
|
|
||||||
|
Result<void> load_file(const os::Path& path);
|
||||||
|
|
||||||
|
Result<void> save_file();
|
||||||
|
|
||||||
|
Result<ui::EventResult> handle_key_event(const ui::KeyEventRequest& request) override;
|
||||||
|
|
||||||
|
Result<void> draw(ui::Canvas& canvas) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SharedPtr<ui::Font> m_font;
|
||||||
|
|
||||||
|
struct Line
|
||||||
|
{
|
||||||
|
usize begin;
|
||||||
|
usize end;
|
||||||
|
};
|
||||||
|
Vector<Line> m_lines;
|
||||||
|
|
||||||
|
os::Path m_path { AT_FDCWD };
|
||||||
|
|
||||||
|
Result<void> recalculate_lines();
|
||||||
|
void recalculate_cursor_position();
|
||||||
|
void recalculate_cursor_index();
|
||||||
|
};
|
39
editor/main.cpp
Normal file
39
editor/main.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Graphical text editor.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "EditorWidget.h"
|
||||||
|
#include <os/ArgumentParser.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
|
||||||
|
Result<int> luna_main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
StringView path;
|
||||||
|
|
||||||
|
os::ArgumentParser parser;
|
||||||
|
parser.add_description("A graphical text editor"_sv);
|
||||||
|
parser.add_system_program_info("editor"_sv);
|
||||||
|
parser.add_positional_argument(path, "path", false);
|
||||||
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
|
ui::App app;
|
||||||
|
TRY(app.init());
|
||||||
|
|
||||||
|
auto* window = TRY(ui::Window::create(ui::Rect { 200, 300, 600, 600 }));
|
||||||
|
window->set_background(ui::Color::from_rgb(40, 40, 40));
|
||||||
|
window->set_title("Text Editor");
|
||||||
|
app.set_main_window(window);
|
||||||
|
|
||||||
|
auto* editor = TRY(make<EditorWidget>(ui::Font::default_font()));
|
||||||
|
window->set_main_widget(*editor);
|
||||||
|
if (!path.is_empty()) TRY(editor->load_file(path));
|
||||||
|
|
||||||
|
window->draw();
|
||||||
|
|
||||||
|
return app.run();
|
||||||
|
}
|
@ -17,6 +17,8 @@ set(SOURCES
|
|||||||
src/Container.cpp
|
src/Container.cpp
|
||||||
src/Button.cpp
|
src/Button.cpp
|
||||||
src/Label.cpp
|
src/Label.cpp
|
||||||
|
src/InputField.cpp
|
||||||
|
src/TextInput.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(ui ${SOURCES})
|
add_library(ui ${SOURCES})
|
||||||
|
40
libui/include/ui/InputField.h
Normal file
40
libui/include/ui/InputField.h
Normal 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 };
|
||||||
|
};
|
||||||
|
}
|
41
libui/include/ui/TextInput.h
Normal file
41
libui/include/ui/TextInput.h
Normal 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
112
libui/src/InputField.cpp
Normal 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
52
libui/src/TextInput.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ Result<void> TerminalWidget::init(char* const* args)
|
|||||||
m_font = ui::Font::default_font();
|
m_font = ui::Font::default_font();
|
||||||
m_bold_font = ui::Font::default_bold_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_terminal_canvas.fill(ui::BLACK);
|
||||||
|
|
||||||
m_cursor_timer = TRY(os::Timer::create_repeating(500, [this]() { this->tick_cursor(); }));
|
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 (did_draw) drawn++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawn > 0) ui::App::the().main_window()->draw();
|
if (drawn > 0) window()->draw();
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ void TerminalWidget::tick_cursor()
|
|||||||
else
|
else
|
||||||
erase_current_char();
|
erase_current_char();
|
||||||
|
|
||||||
ui::App::the().main_window()->draw();
|
window()->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
|
void TerminalWidget::draw_glyph(wchar_t c, int x, int y)
|
||||||
|
Loading…
Reference in New Issue
Block a user