Compare commits
18 Commits
ce1e9abc39
...
f3d9d4bcc0
Author | SHA1 | Date | |
---|---|---|---|
f3d9d4bcc0 | |||
d6f7069589 | |||
ec44213f4e | |||
a06ecef08d | |||
43c0c801ae | |||
0c8fe315db | |||
2cf6549608 | |||
88a6beff5a | |||
615cae687d | |||
ff10e5f3b2 | |||
71df91b4a0 | |||
7dc4b17d46 | |||
332976dde9 | |||
5b94217316 | |||
7205020bac | |||
898eb43360 | |||
a863b17746 | |||
5087b6db30 |
@ -53,3 +53,4 @@ add_subdirectory(tests)
|
||||
add_subdirectory(shell)
|
||||
add_subdirectory(wind)
|
||||
add_subdirectory(terminal)
|
||||
add_subdirectory(editor)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Luna
|
||||
A simple POSIX-based operating system for personal computers, written in C++. [![Build Status](https://drone.cloudapio.eu/api/badges/apio/Luna/status.svg)](https://drone.cloudapio.eu/apio/Luna)
|
||||
A simple POSIX-based operating system for personal computers, written in C++.
|
||||
|
||||
## Another UNIX clone?
|
||||
[Yes, another UNIX clone](https://wiki.osdev.org/User:Sortie/Yes_Another_Unix_Clone).
|
||||
|
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();
|
||||
}
|
@ -98,8 +98,13 @@ extern "C"
|
||||
* endptr if nonnull. */
|
||||
unsigned long strtoul(const char* str, char** endptr, int base);
|
||||
|
||||
#define strtoll strtol
|
||||
#define strtoull strtoul
|
||||
/* Parse an integer of the specified base from a string, storing the first non-number character in endptr if
|
||||
* nonnull. */
|
||||
long long strtoll(const char* str, char** endptr, int base);
|
||||
|
||||
/* Parse an unsigned integer of the specified base from a string, storing the first non-number character in
|
||||
* endptr if nonnull. */
|
||||
unsigned long long strtoull(const char* str, char** endptr, int base);
|
||||
|
||||
/* Return the next pseudorandom number. */
|
||||
int rand();
|
||||
|
@ -22,6 +22,7 @@
|
||||
#define _POSIX_SYNCHRONIZED_IO 200112L
|
||||
#define _POSIX_SAVED_IDS 200112L
|
||||
#define _POSIX_VDISABLE (-2)
|
||||
#define _POSIX_VERSION 200112L
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
|
@ -101,6 +101,16 @@ extern "C"
|
||||
return rc;
|
||||
}
|
||||
|
||||
long long strtoll(const char* str, char** endptr, int base)
|
||||
{
|
||||
return strtol(str, endptr, base);
|
||||
}
|
||||
|
||||
unsigned long long strtoull(const char* str, char** endptr, int base)
|
||||
{
|
||||
return strtoul(str, endptr, base);
|
||||
}
|
||||
|
||||
__noreturn void abort()
|
||||
{
|
||||
// First, try to unblock SIGABRT and then raise it.
|
||||
|
@ -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})
|
||||
|
@ -69,5 +69,13 @@ namespace ui
|
||||
* @param stride The number of pixels to skip to go to the next line.
|
||||
*/
|
||||
void fill(u32* pixels, int stride);
|
||||
|
||||
/**
|
||||
* @brief Fill the canvas with pixels, without doing any extra processing.
|
||||
*
|
||||
* @param pixels The array of pixels (must be at least width*height).
|
||||
* @param stride The number of pixels to skip to go to the next line.
|
||||
*/
|
||||
void copy(u32* pixels, int stride);
|
||||
};
|
||||
};
|
||||
|
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);
|
||||
};
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <luna/CString.h>
|
||||
#include <ui/Canvas.h>
|
||||
|
||||
namespace ui
|
||||
@ -59,4 +60,16 @@ namespace ui
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::copy(u32* pixels, int _stride)
|
||||
{
|
||||
u8* p = ptr;
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
u32* colorp = (u32*)p;
|
||||
memcpy(colorp, pixels, width * sizeof(u32));
|
||||
pixels += _stride;
|
||||
p += stride * sizeof(Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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();
|
||||
}
|
||||
}
|
@ -13,8 +13,10 @@ Name | Version | Description | URL
|
||||
---|---|--- | ---
|
||||
bc | 6.6.0 | An implementation of the POSIX bc calculator | https://github.com/gavinhoward/bc
|
||||
binutils | 2.39 | The GNU suite of binary utilities | https://www.gnu.org/software/binutils
|
||||
doomgeneric | 0.0.1 | Easily portable doom | https://github.com/ozkl/doomgeneric
|
||||
gcc | 12.2.0 | The GNU Compiler Collection | https://www.gnu.org/software/gcc
|
||||
gmp | 6.3.0 | The GNU Multiple Precision Arithmetic Library | https://gmplib.org
|
||||
libwind | 0.0.1 | A standalone implementation of the wind client protocol | https://git.cloudapio.eu/apio/libwind
|
||||
minitar | 1.7.5 | Tiny and easy-to-use C library to read/write tar archives | https://git.cloudapio.eu/apio/minitar
|
||||
mpc | 1.3.1 | The GNU Multiple Precision Complex Library | https://www.multiprecision.org
|
||||
mpfr | 4.2.0 | The GNU Multiple Precision Floating-Point Reliable Library | https://mpfr.org
|
||||
|
32
ports/doomgeneric/PACKAGE
Normal file
32
ports/doomgeneric/PACKAGE
Normal file
@ -0,0 +1,32 @@
|
||||
# Basic information
|
||||
name="doomgeneric"
|
||||
version="0.0.1"
|
||||
dependencies=(libwind)
|
||||
|
||||
# Download options
|
||||
format="git"
|
||||
url="https://github.com/ozkl/doomgeneric.git"
|
||||
|
||||
# Build instructions
|
||||
do_patch()
|
||||
{
|
||||
patch -ui $portdir/doomgeneric.patch -p 1 -d $srcdir
|
||||
}
|
||||
|
||||
do_configure()
|
||||
{
|
||||
:
|
||||
}
|
||||
|
||||
do_build()
|
||||
{
|
||||
cd $srcdir/doomgeneric
|
||||
make
|
||||
}
|
||||
|
||||
do_install()
|
||||
{
|
||||
cd $srcdir/doomgeneric
|
||||
mkdir -p $installdir/usr/bin/
|
||||
cp doomgeneric $installdir/usr/bin/
|
||||
}
|
197
ports/doomgeneric/doomgeneric.patch
Normal file
197
ports/doomgeneric/doomgeneric.patch
Normal file
@ -0,0 +1,197 @@
|
||||
diff --git a/doomgeneric/Makefile b/doomgeneric/Makefile
|
||||
index 503e0dc..5c44a6d 100644
|
||||
--- a/doomgeneric/Makefile
|
||||
+++ b/doomgeneric/Makefile
|
||||
@@ -12,17 +12,16 @@ else
|
||||
endif
|
||||
|
||||
|
||||
-CC=clang # gcc or g++
|
||||
-CFLAGS+=-ggdb3 -Os
|
||||
+CC=$(LUNA_ARCH)-luna-gcc
|
||||
LDFLAGS+=-Wl,--gc-sections
|
||||
-CFLAGS+=-ggdb3 -Wall -DNORMALUNIX -DLINUX -DSNDSERV -D_DEFAULT_SOURCE # -DUSEASM
|
||||
-LIBS+=-lm -lc -lX11
|
||||
+CFLAGS+=-ggdb3 -Os -Wall -fno-omit-frame-pointer -DNORMALUNIX -DLINUX -DSNDSERV -D_DEFAULT_SOURCE # -DUSEASM
|
||||
+LIBS+=-lc -lwind
|
||||
|
||||
# subdirectory for objects
|
||||
OBJDIR=build
|
||||
OUTPUT=doomgeneric
|
||||
|
||||
-SRC_DOOM = dummy.o am_map.o doomdef.o doomstat.o dstrings.o d_event.o d_items.o d_iwad.o d_loop.o d_main.o d_mode.o d_net.o f_finale.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o i_cdmus.o i_endoom.o i_joystick.o i_scale.o i_sound.o i_system.o i_timer.o memio.o m_argv.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o m_menu.o m_misc.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o sha1.o sounds.o statdump.o st_lib.o st_stuff.o s_sound.o tables.o v_video.o wi_stuff.o w_checksum.o w_file.o w_main.o w_wad.o z_zone.o w_file_stdc.o i_input.o i_video.o doomgeneric.o doomgeneric_xlib.o
|
||||
+SRC_DOOM = dummy.o am_map.o doomdef.o doomstat.o dstrings.o d_event.o d_items.o d_iwad.o d_loop.o d_main.o d_mode.o d_net.o f_finale.o f_wipe.o g_game.o hu_lib.o hu_stuff.o info.o i_cdmus.o i_endoom.o i_joystick.o i_scale.o i_sound.o i_system.o i_timer.o memio.o m_argv.o m_bbox.o m_cheat.o m_config.o m_controls.o m_fixed.o m_menu.o m_misc.o m_random.o p_ceilng.o p_doors.o p_enemy.o p_floor.o p_inter.o p_lights.o p_map.o p_maputl.o p_mobj.o p_plats.o p_pspr.o p_saveg.o p_setup.o p_sight.o p_spec.o p_switch.o p_telept.o p_tick.o p_user.o r_bsp.o r_data.o r_draw.o r_main.o r_plane.o r_segs.o r_sky.o r_things.o sha1.o sounds.o statdump.o st_lib.o st_stuff.o s_sound.o tables.o v_video.o wi_stuff.o w_checksum.o w_file.o w_main.o w_wad.o z_zone.o w_file_stdc.o i_input.o i_video.o doomgeneric.o doomgeneric_luna.o
|
||||
OBJS += $(addprefix $(OBJDIR)/, $(SRC_DOOM))
|
||||
|
||||
all: $(OUTPUT)
|
||||
@@ -51,4 +50,3 @@ $(OBJDIR)/%.o: %.c
|
||||
|
||||
print:
|
||||
@echo OBJS: $(OBJS)
|
||||
-
|
||||
diff --git a/doomgeneric/doomgeneric_luna.c b/doomgeneric/doomgeneric_luna.c
|
||||
new file mode 100644
|
||||
index 0000000..4d37ddc
|
||||
--- /dev/null
|
||||
+++ b/doomgeneric/doomgeneric_luna.c
|
||||
@@ -0,0 +1,160 @@
|
||||
+#include "doomgeneric.h"
|
||||
+#include "wind.h"
|
||||
+#include <string.h>
|
||||
+#include <time.h>
|
||||
+#include <unistd.h>
|
||||
+#include <sys/time.h>
|
||||
+#include <stdio.h>
|
||||
+#include "doomkeys.h"
|
||||
+
|
||||
+static wind_client_t g_client;
|
||||
+static wind_window_t g_window;
|
||||
+
|
||||
+#define KEYQUEUE_SIZE 16
|
||||
+
|
||||
+static unsigned short s_KeyQueue[KEYQUEUE_SIZE];
|
||||
+static unsigned int s_KeyQueueWriteIndex = 0;
|
||||
+static unsigned int s_KeyQueueReadIndex = 0;
|
||||
+
|
||||
+static unsigned char convertToDoomKey(enum wind_keycode scancode)
|
||||
+{
|
||||
+ unsigned char key = 0;
|
||||
+
|
||||
+ switch (scancode)
|
||||
+ {
|
||||
+ case wind_key_Enter:
|
||||
+ key = KEY_ENTER;
|
||||
+ break;
|
||||
+ case wind_key_Esc:
|
||||
+ key = KEY_ESCAPE;
|
||||
+ break;
|
||||
+ case wind_key_LeftArrow:
|
||||
+ key = KEY_LEFTARROW;
|
||||
+ break;
|
||||
+ case wind_key_RightArrow:
|
||||
+ key = KEY_RIGHTARROW;
|
||||
+ break;
|
||||
+ case wind_key_UpArrow:
|
||||
+ key = KEY_UPARROW;
|
||||
+ break;
|
||||
+ case wind_key_DownArrow:
|
||||
+ key = KEY_DOWNARROW;
|
||||
+ break;
|
||||
+ case wind_key_LeftControl:
|
||||
+ key = KEY_FIRE;
|
||||
+ break;
|
||||
+ case wind_key_RightControl:
|
||||
+ key = KEY_USE;
|
||||
+ break;
|
||||
+ case wind_key_RightShift:
|
||||
+ key = KEY_RSHIFT;
|
||||
+ break;
|
||||
+ case wind_key_CH18:
|
||||
+ key = 'y';
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return key;
|
||||
+}
|
||||
+
|
||||
+static void addKeyToQueue(int pressed, enum wind_keycode keyCode)
|
||||
+{
|
||||
+ unsigned char key = convertToDoomKey(keyCode);
|
||||
+
|
||||
+ unsigned short keyData = (pressed << 8) | key;
|
||||
+
|
||||
+ s_KeyQueue[s_KeyQueueWriteIndex] = keyData;
|
||||
+ s_KeyQueueWriteIndex++;
|
||||
+ s_KeyQueueWriteIndex %= KEYQUEUE_SIZE;
|
||||
+}
|
||||
+
|
||||
+static int event_handler(wind_client_t*, uint8_t event, void*)
|
||||
+{
|
||||
+ switch (event)
|
||||
+ {
|
||||
+ case wind_MouseEvent: {
|
||||
+ struct wind_mouse_event_request event_data;
|
||||
+ wind_read_response(&g_client, event, &event_data, sizeof event_data);
|
||||
+ break;
|
||||
+ }
|
||||
+ case wind_MouseLeave: {
|
||||
+ struct wind_mouse_leave_request event_data;
|
||||
+ wind_read_response(&g_client, event, &event_data, sizeof event_data);
|
||||
+ break;
|
||||
+ }
|
||||
+ case wind_KeyEvent: {
|
||||
+ struct wind_key_event_request event_data;
|
||||
+ wind_read_response(&g_client, event, &event_data, sizeof event_data);
|
||||
+ addKeyToQueue(event_data.pressed, event_data.code);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+void DG_Init() {
|
||||
+ wind_connect(&g_client, WIND_SOCKET_PATH, 0);
|
||||
+ wind_set_handler(&g_client, event_handler, NULL);
|
||||
+
|
||||
+ struct wind_rect rect = {
|
||||
+ .pos = {0,0},
|
||||
+ .width = DOOMGENERIC_RESX,
|
||||
+ .height =DOOMGENERIC_RESY
|
||||
+ };
|
||||
+
|
||||
+ int result = wind_create_window(&g_client, &g_window, rect);
|
||||
+ if(result < 0) perror("wind_create_window");
|
||||
+}
|
||||
+
|
||||
+void DG_DrawFrame() {
|
||||
+ wind_check_for_messages(&g_client);
|
||||
+
|
||||
+ memcpy(g_window.canvas, DG_ScreenBuffer, DOOMGENERIC_RESX*DOOMGENERIC_RESY*4);
|
||||
+ wind_invalidate(&g_client, &g_window);
|
||||
+}
|
||||
+
|
||||
+int DG_GetKey(int *pressed, unsigned char *doomKey) { if (s_KeyQueueReadIndex == s_KeyQueueWriteIndex)
|
||||
+ {
|
||||
+ //key queue is empty
|
||||
+
|
||||
+ return 0;
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ unsigned short keyData = s_KeyQueue[s_KeyQueueReadIndex];
|
||||
+ s_KeyQueueReadIndex++;
|
||||
+ s_KeyQueueReadIndex %= KEYQUEUE_SIZE;
|
||||
+
|
||||
+ *pressed = keyData >> 8;
|
||||
+ *doomKey = keyData & 0xFF;
|
||||
+
|
||||
+ return 1;
|
||||
+ } }
|
||||
+
|
||||
+void DG_SleepMs(uint32_t ms) { usleep(ms * 1000); }
|
||||
+
|
||||
+void DG_SetWindowTitle(const char *title) { wind_set_window_title(&g_client, &g_window, title); }
|
||||
+
|
||||
+uint32_t DG_GetTicksMs() {
|
||||
+ struct timespec ts;
|
||||
+ clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
+ return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
|
||||
+}
|
||||
+
|
||||
+int main(int argc, char **argv)
|
||||
+{
|
||||
+ doomgeneric_Create(argc, argv);
|
||||
+
|
||||
+ while (1)
|
||||
+ {
|
||||
+ doomgeneric_Tick();
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
26
ports/libwind/PACKAGE
Normal file
26
ports/libwind/PACKAGE
Normal file
@ -0,0 +1,26 @@
|
||||
# Basic information
|
||||
name="libwind"
|
||||
version="0.0.1"
|
||||
|
||||
# Download options
|
||||
format="git"
|
||||
url="https://git.cloudapio.eu/apio/libwind.git"
|
||||
|
||||
# Build instructions
|
||||
do_configure()
|
||||
{
|
||||
:
|
||||
}
|
||||
|
||||
do_build()
|
||||
{
|
||||
cd $srcdir
|
||||
make
|
||||
}
|
||||
|
||||
do_install()
|
||||
{
|
||||
export DESTDIR=$installdir/usr
|
||||
cd $srcdir
|
||||
make install
|
||||
}
|
@ -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)
|
||||
|
0
tools/exec/luna-pkg-config
Normal file → Executable file
0
tools/exec/luna-pkg-config
Normal file → Executable file
@ -83,6 +83,7 @@ case $SUBCOMMAND in
|
||||
;;
|
||||
generate-diff)
|
||||
cd ${srcdir:-$WORKDIR/$name-$version}
|
||||
git add -N .
|
||||
git diff > $LUNA_ROOT/ports/$name/$name.patch
|
||||
echo "Created $LUNA_ROOT/ports/$name/$name.patch. Remember to add patching code to the PACKAGE file!"
|
||||
;;
|
||||
|
16
wind/IPC.cpp
16
wind/IPC.cpp
@ -19,11 +19,11 @@
|
||||
_expr_rc.release_value(); \
|
||||
})
|
||||
|
||||
#define CHECK_WINDOW_ID(request) \
|
||||
#define CHECK_WINDOW_ID(request, context) \
|
||||
do { \
|
||||
if ((usize)request.window >= client.windows.size() || !client.windows[request.window]) \
|
||||
{ \
|
||||
os::eprintln("wind: Window id is invalid!"); \
|
||||
os::eprintln("wind: Window id is invalid! (%s)", context); \
|
||||
return {}; \
|
||||
} \
|
||||
} while (0)
|
||||
@ -76,7 +76,7 @@ static Result<void> handle_remove_shm_message(Client& client)
|
||||
ui::RemoveSharedMemoryRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "RemoveShm");
|
||||
|
||||
shm_unlink(client.windows[request.window]->shm_path.chars());
|
||||
|
||||
@ -92,7 +92,7 @@ static Result<void> handle_set_window_title_message(Client& client)
|
||||
|
||||
os::println("wind: SetWindowTitle(\"%s\") for window %d", name.chars(), request.window);
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "SetWindowTitle");
|
||||
|
||||
client.windows[request.window]->name = move(name);
|
||||
|
||||
@ -104,7 +104,7 @@ static Result<void> handle_invalidate_message(Client& client)
|
||||
ui::InvalidateRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "Invalidate");
|
||||
|
||||
client.windows[request.window]->dirty = true;
|
||||
|
||||
@ -116,7 +116,7 @@ static Result<void> handle_close_window_message(Client& client)
|
||||
ui::CloseWindowRequest request;
|
||||
if (!TRY(client.conn->read_message(request))) return {};
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "CloseWindow");
|
||||
|
||||
auto* window = client.windows[request.window];
|
||||
client.windows[request.window] = nullptr;
|
||||
@ -146,7 +146,7 @@ static Result<void> handle_set_titlebar_height_message(Client& client)
|
||||
|
||||
if (request.height < 0) request.height = 0;
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "SetTitlebarHeight");
|
||||
|
||||
auto* window = client.windows[request.window];
|
||||
|
||||
@ -173,7 +173,7 @@ static Result<void> handle_set_special_window_attributes_message(Client& client)
|
||||
return {};
|
||||
}
|
||||
|
||||
CHECK_WINDOW_ID(request);
|
||||
CHECK_WINDOW_ID(request, "SetSpecialWindowAttributes");
|
||||
|
||||
client.windows[request.window]->attributes = request.attributes;
|
||||
|
||||
|
@ -12,7 +12,7 @@ void Window::draw(ui::Canvas& screen)
|
||||
dirty = false;
|
||||
|
||||
auto window = screen.subcanvas(surface);
|
||||
window.fill(pixels, surface.width);
|
||||
window.copy(pixels, surface.width);
|
||||
}
|
||||
|
||||
void Window::focus()
|
||||
|
Loading…
Reference in New Issue
Block a user