FontEditor: Support selection ranges

This makes editing much easier, e.g. you don't need longer to copy
hundreds of glyphs one by one.

It has some flaws, e.g. it's not integrated with undo stack,
but we need to start with something!
This commit is contained in:
Maciej 2022-01-04 18:24:06 +01:00 committed by Andreas Kling
parent ad2551e6b8
commit 1d98499234
Notes: sideshowbarker 2024-07-17 21:23:40 +09:00
6 changed files with 283 additions and 163 deletions

View File

@ -179,9 +179,7 @@ FontEditorWidget::FontEditorWidget()
save_as(save_path.value());
});
m_cut_action = GUI::CommonActions::make_cut_action([&](auto&) {
if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->cut_glyph();
cut_selected_glyphs();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
else
@ -189,23 +187,15 @@ FontEditorWidget::FontEditorWidget()
update_statusbar();
});
m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
if (!m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->copy_glyph();
copy_selected_glyphs();
});
m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
m_glyph_editor_widget->paste_glyph();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(true, GUI::AllowCallback::No);
else
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
paste_glyphs();
update_statusbar();
});
m_paste_action->set_enabled(GUI::Clipboard::the().fetch_mime_type() == "glyph/x-fonteditor");
m_delete_action = GUI::CommonActions::make_delete_action([this](auto&) {
if (m_glyph_editor_widget->is_glyph_empty() && !m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()))
return;
m_glyph_editor_widget->delete_glyph();
delete_selected_glyphs();
if (m_edited_font->is_fixed_width())
m_glyph_editor_present_checkbox->set_checked(false, GUI::AllowCallback::No);
else
@ -237,14 +227,14 @@ FontEditorWidget::FontEditorWidget()
int code_point = strtoul(&input[0], nullptr, 16);
code_point = clamp(code_point, 0x0000, 0x10FFFF);
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(code_point);
m_glyph_map_widget->set_active_glyph(code_point);
m_glyph_map_widget->scroll_to_glyph(code_point);
}
});
m_go_to_glyph_action->set_status_tip("Go to the specified code point");
m_previous_glyph_action = GUI::Action::create("Pre&vious Glyph", { Mod_Alt, Key_Left }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
bool search_wrapped = false;
for (int i = m_glyph_map_widget->selected_glyph() - 1;; --i) {
for (int i = m_glyph_map_widget->active_glyph() - 1;; --i) {
if (i < 0 && !search_wrapped) {
i = 0x10FFFF;
search_wrapped = true;
@ -253,7 +243,7 @@ FontEditorWidget::FontEditorWidget()
}
if (m_edited_font->contains_raw_glyph(i)) {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(i);
m_glyph_map_widget->set_active_glyph(i);
m_glyph_map_widget->scroll_to_glyph(i);
break;
}
@ -262,7 +252,7 @@ FontEditorWidget::FontEditorWidget()
m_previous_glyph_action->set_status_tip("Seek the previous visible glyph");
m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
bool search_wrapped = false;
for (int i = m_glyph_map_widget->selected_glyph() + 1;; ++i) {
for (int i = m_glyph_map_widget->active_glyph() + 1;; ++i) {
if (i > 0x10FFFF && !search_wrapped) {
i = 0;
search_wrapped = true;
@ -271,7 +261,7 @@ FontEditorWidget::FontEditorWidget()
}
if (m_edited_font->contains_raw_glyph(i)) {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->set_selected_glyph(i);
m_glyph_map_widget->set_active_glyph(i);
m_glyph_map_widget->scroll_to_glyph(i);
break;
}
@ -377,7 +367,7 @@ FontEditorWidget::FontEditorWidget()
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
};
m_glyph_map_widget->on_glyph_selected = [this](int glyph) {
m_glyph_map_widget->on_active_glyph_changed = [this](int glyph) {
if (m_undo_glyph)
m_undo_glyph->set_code_point(glyph);
m_glyph_editor_widget->set_glyph(glyph);
@ -401,7 +391,7 @@ FontEditorWidget::FontEditorWidget()
m_fixed_width_checkbox->on_checked = [this](bool checked) {
m_edited_font->set_fixed_width(checked);
auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph());
auto glyph_width = m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph());
m_glyph_editor_width_spinbox->set_visible(!checked);
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_visible(checked);
@ -413,9 +403,9 @@ FontEditorWidget::FontEditorWidget()
m_glyph_editor_width_spinbox->on_change = [this](int value) {
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), value);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), value);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
update_preview();
update_statusbar();
did_modify_font();
@ -423,9 +413,9 @@ FontEditorWidget::FontEditorWidget()
m_glyph_editor_present_checkbox->on_checked = [this](bool checked) {
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), checked ? m_edited_font->glyph_fixed_width() : 0);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->update_glyph(m_glyph_map_widget->active_glyph());
update_preview();
update_statusbar();
did_modify_font();
@ -504,10 +494,10 @@ void FontEditorWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&&
m_glyph_editor_width_spinbox->set_visible(!m_edited_font->is_fixed_width());
m_glyph_editor_width_spinbox->set_max(m_edited_font->max_glyph_width(), GUI::AllowCallback::No);
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
m_glyph_editor_width_spinbox->set_value(m_edited_font->raw_glyph_width(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_visible(m_edited_font->is_fixed_width());
m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->selected_glyph()), GUI::AllowCallback::No);
m_glyph_editor_present_checkbox->set_checked(m_edited_font->contains_raw_glyph(m_glyph_map_widget->active_glyph()), GUI::AllowCallback::No);
m_fixed_width_checkbox->set_checked(m_edited_font->is_fixed_width(), GUI::AllowCallback::No);
m_name_textbox->set_text(m_edited_font->name(), GUI::AllowCallback::No);
@ -540,12 +530,12 @@ void FontEditorWidget::initialize(String const& path, RefPtr<Gfx::BitmapFont>&&
deferred_invoke([this] {
m_glyph_map_widget->set_focus(true);
m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->selected_glyph());
m_glyph_map_widget->scroll_to_glyph(m_glyph_map_widget->active_glyph());
update_title();
});
m_undo_stack = make<GUI::UndoStack>();
m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->selected_glyph(), *m_edited_font));
m_undo_glyph = adopt_ref(*new UndoGlyph(m_glyph_map_widget->active_glyph(), *m_edited_font));
m_undo_stack->on_state_change = [this] {
m_undo_action->set_enabled(m_undo_stack->can_undo());
@ -651,14 +641,14 @@ void FontEditorWidget::undo()
m_undo_stack->undo();
auto glyph = m_undo_glyph->restored_code_point();
auto glyph_width = m_undo_glyph->restored_width();
m_glyph_map_widget->set_selected_glyph(glyph);
m_glyph_map_widget->set_active_glyph(glyph);
m_glyph_map_widget->scroll_to_glyph(glyph);
if (m_edited_font->is_fixed_width()) {
m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
} else {
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
}
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), glyph_width);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(glyph);
update_preview();
@ -672,14 +662,14 @@ void FontEditorWidget::redo()
m_undo_stack->redo();
auto glyph = m_undo_glyph->restored_code_point();
auto glyph_width = m_undo_glyph->restored_width();
m_glyph_map_widget->set_selected_glyph(glyph);
m_glyph_map_widget->set_active_glyph(glyph);
m_glyph_map_widget->scroll_to_glyph(glyph);
if (m_edited_font->is_fixed_width()) {
m_glyph_editor_present_checkbox->set_checked(glyph_width > 0, GUI::AllowCallback::No);
} else {
m_glyph_editor_width_spinbox->set_value(glyph_width, GUI::AllowCallback::No);
}
m_edited_font->set_glyph_width(m_glyph_map_widget->selected_glyph(), glyph_width);
m_edited_font->set_glyph_width(m_glyph_map_widget->active_glyph(), glyph_width);
m_glyph_editor_widget->update();
m_glyph_map_widget->update_glyph(glyph);
update_preview();
@ -722,7 +712,7 @@ void FontEditorWidget::did_modify_font()
void FontEditorWidget::update_statusbar()
{
auto glyph = m_glyph_map_widget->selected_glyph();
auto glyph = m_glyph_map_widget->active_glyph();
StringBuilder builder;
builder.appendff("U+{:04X} (", glyph);
@ -803,3 +793,118 @@ void FontEditorWidget::set_scale_and_save(i32 scale)
Config::write_i32("FontEditor", "GlyphEditor", "Scale", scale);
did_resize_glyph_editor();
}
void FontEditorWidget::copy_selected_glyphs()
{
ByteBuffer buffer;
int first_glyph = -1;
size_t glyph_count = 0;
auto append_glyph_to_buffer = [&](int glyph) {
if (!edited_font().contains_glyph(glyph))
return;
if (first_glyph == -1 || glyph < first_glyph)
first_glyph = glyph;
buffer.append({ (char*)&glyph, sizeof(int) });
auto bitmap = edited_font().raw_glyph(glyph).glyph_bitmap();
buffer.append((u8)bitmap.width());
buffer.append((u8)bitmap.height());
for (int x = 0; x < bitmap.width(); x++) {
for (int y = 0; y < bitmap.height(); y++)
buffer.append(bitmap.bit_at(x, y));
}
glyph_count++;
};
auto selection = m_glyph_map_widget->selection().normalized();
for (int i = selection.start(); i < selection.start() + selection.size(); i++)
append_glyph_to_buffer(i);
HashMap<String, String> metadata;
metadata.set("first_glyph", String::number(first_glyph));
metadata.set("count", String::number(glyph_count));
GUI::Clipboard::the().set_data(buffer.bytes(), "glyph/x-fonteditor", metadata);
}
void FontEditorWidget::cut_selected_glyphs()
{
copy_selected_glyphs();
delete_selected_glyphs();
}
void FontEditorWidget::paste_glyphs()
{
auto [data, mime_type, metadata] = GUI::Clipboard::the().fetch_data_and_type();
if (!mime_type.starts_with("glyph/"))
return;
;
auto glyph_count = metadata.get("count").value_or("0").to_uint().value_or(0);
if (glyph_count == 0)
return;
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if (glyph_count == 1) {
if (m_glyph_editor_widget->on_undo_event)
m_glyph_editor_widget->on_undo_event();
}
auto first_glyph = metadata.get("first_glyph").value_or("0").to_uint().value_or(0);
InputMemoryStream stream(data.bytes());
for (size_t s = 0; s < glyph_count; s++) {
int copied_glyph {};
char width {};
char height {};
stream >> Bytes { (char*)&copied_glyph, sizeof(int) } >> width >> height;
if (stream.has_any_error()) {
dbgln("Failed to read glyph from clipboard, aborting!");
return;
}
int glyph = m_glyph_map_widget->active_glyph() + (copied_glyph - first_glyph);
auto bitmap = edited_font().raw_glyph(glyph).glyph_bitmap();
m_edited_font->set_glyph_width(glyph, min(width, edited_font().max_glyph_width()));
for (int x = 0; x < min(width, edited_font().max_glyph_width()); x++) {
for (int y = 0; y < min(height, edited_font().glyph_height()); y++) {
char byte;
stream >> byte;
if (stream.has_any_error()) {
dbgln("Failed to read glyph from clipboard, aborting!");
return;
}
bitmap.set_bit_at(x, y, byte);
}
}
if (m_glyph_editor_widget->on_glyph_altered)
m_glyph_editor_widget->on_glyph_altered(glyph);
}
m_glyph_editor_widget->update();
}
void FontEditorWidget::delete_selected_glyphs()
{
auto delete_glyph = [&](int glyph) {
auto bitmap = m_edited_font->raw_glyph(glyph).glyph_bitmap();
m_edited_font->set_glyph_width(glyph, 0);
for (int x = 0; x < m_edited_font->max_glyph_width(); x++)
for (int y = 0; y < m_edited_font->glyph_height(); y++)
bitmap.set_bit_at(x, y, false);
if (m_glyph_editor_widget->on_glyph_altered)
m_glyph_editor_widget->on_glyph_altered(glyph);
};
auto selection = m_glyph_map_widget->selection().normalized();
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if (selection.size() == 1) {
if (m_glyph_editor_widget->on_undo_event)
m_glyph_editor_widget->on_undo_event();
}
for (int i = selection.start(); i < selection.start() + selection.size(); i++)
delete_glyph(i);
}

View File

@ -55,6 +55,11 @@ private:
void set_scale(i32);
void set_scale_and_save(i32);
void copy_selected_glyphs();
void cut_selected_glyphs();
void paste_glyphs();
void delete_selected_glyphs();
RefPtr<Gfx::BitmapFont> m_edited_font;
RefPtr<GlyphMapWidget> m_glyph_map_widget;

View File

@ -33,98 +33,6 @@ void GlyphEditorWidget::set_glyph(int glyph)
update();
}
void GlyphEditorWidget::delete_glyph()
{
if (on_undo_event)
on_undo_event();
auto bitmap = font().raw_glyph(m_glyph).glyph_bitmap();
for (int x = 0; x < font().max_glyph_width(); x++)
for (int y = 0; y < font().glyph_height(); y++)
bitmap.set_bit_at(x, y, false);
font().set_glyph_width(m_glyph, 0);
if (on_glyph_altered)
on_glyph_altered(m_glyph);
update();
}
void GlyphEditorWidget::cut_glyph()
{
copy_glyph();
delete_glyph();
}
void GlyphEditorWidget::copy_glyph()
{
auto bitmap = font().raw_glyph(m_glyph).glyph_bitmap();
u8 bits[bitmap.width()][bitmap.height()];
for (int x = 0; x < bitmap.width(); x++) {
for (int y = 0; y < bitmap.height(); y++) {
bits[x][y] = bitmap.bit_at(x, y);
}
}
StringBuilder glyph_builder;
if (AK::UnicodeUtils::is_unicode_control_code_point(m_glyph))
glyph_builder.append(AK::UnicodeUtils::get_unicode_control_code_point_alias(m_glyph).value());
else if (Gfx::get_char_bidi_class(m_glyph) == Gfx::BidirectionalClass::STRONG_RTL)
glyph_builder.append_code_point(0xFFFD);
else
glyph_builder.append_code_point(m_glyph);
HashMap<String, String> metadata;
metadata.set("char", glyph_builder.to_string());
metadata.set("width", String::number(bitmap.width()));
metadata.set("height", String::number(bitmap.height()));
GUI::Clipboard::the().set_data(ReadonlyBytes(&bits[0], bitmap.width() * bitmap.height()), "glyph/x-fonteditor", metadata);
}
void GlyphEditorWidget::paste_glyph()
{
auto [data, mime_type, metadata] = GUI::Clipboard::the().fetch_data_and_type();
if (!mime_type.starts_with("glyph/"))
return;
auto byte_buffer = data.data();
auto buffer_height = metadata.get("height").value_or("0").to_int().value_or(0);
auto buffer_width = metadata.get("width").value_or("0").to_int().value_or(0);
if (buffer_height <= 0 || buffer_width <= 0 || buffer_height > 128 || buffer_width > 128) {
dbgln("Refusing to receive glyph of dimensions {}x{}", buffer_width, buffer_height);
return;
}
if (data.size() != static_cast<size_t>(buffer_width * buffer_height)) {
dbgln("Refusing to receive glyph with mismatching buffer sizes: Expected {}x{}={} bytes, received {} bytes.",
buffer_width, buffer_height, buffer_width * buffer_height, data.size());
return;
}
if (on_undo_event)
on_undo_event();
u8 bits[buffer_width][buffer_height];
int i = 0;
for (int x = 0; x < buffer_width; x++) {
for (int y = 0; y < buffer_height; y++) {
bits[x][y] = byte_buffer[i];
i++;
}
}
auto bitmap = font().raw_glyph(m_glyph).glyph_bitmap();
if (bitmap.width() < buffer_width)
font().set_glyph_width(m_glyph, min(buffer_width, font().max_glyph_width()));
for (int x = 0; x < min(buffer_width, font().max_glyph_width()); x++) {
for (int y = 0; y < min(buffer_height, font().glyph_height()); y++) {
bitmap.set_bit_at(x, y, bits[x][y]);
}
}
if (on_glyph_altered)
on_glyph_altered(m_glyph);
update();
}
void GlyphEditorWidget::paint_event(GUI::PaintEvent& event)
{
GUI::Frame::paint_event(event);

View File

@ -29,11 +29,6 @@ public:
int glyph() const { return m_glyph; }
void set_glyph(int);
void cut_glyph();
void copy_glyph();
void paste_glyph();
void delete_glyph();
bool is_glyph_empty();
void rotate_90(Direction);

View File

@ -6,11 +6,44 @@
*/
#include "GlyphMapWidget.h"
#include <AK/MemoryStream.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/Painter.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Emoji.h>
#include <LibGfx/Palette.h>
GlyphMapWidget::Selection GlyphMapWidget::Selection::normalized() const
{
if (m_size > 0)
return *this;
return { m_start + m_size, -m_size + 1 };
}
void GlyphMapWidget::Selection::resize_by(int i)
{
m_size += i;
if (m_size == 0) {
if (i < 0)
m_size--;
else
m_size++;
}
}
bool GlyphMapWidget::Selection::contains(int i) const
{
auto this_normalized = normalized();
return i >= this_normalized.m_start && i < this_normalized.m_start + this_normalized.m_size;
}
void GlyphMapWidget::Selection::extend_to(int glyph)
{
m_size = glyph - m_start;
if (m_size > 0)
m_size++;
}
GlyphMapWidget::GlyphMapWidget()
{
set_focus_policy(GUI::FocusPolicy::StrongFocus);
@ -27,7 +60,7 @@ void GlyphMapWidget::initialize(Gfx::BitmapFont& mutable_font)
return;
m_font = mutable_font;
vertical_scrollbar().set_step(font().glyph_height() + m_vertical_spacing);
set_selected_glyph('A');
set_active_glyph('A');
}
void GlyphMapWidget::resize_event(GUI::ResizeEvent& event)
@ -45,18 +78,22 @@ void GlyphMapWidget::resize_event(GUI::ResizeEvent& event)
int content_height = rows() * (font().glyph_height() + m_vertical_spacing) + frame_thickness();
set_content_size({ content_width, content_height });
scroll_to_glyph(m_selected_glyph);
scroll_to_glyph(m_active_glyph);
AbstractScrollableWidget::resize_event(event);
}
void GlyphMapWidget::set_selected_glyph(int glyph)
void GlyphMapWidget::set_active_glyph(int glyph, ShouldResetSelection should_reset_selection)
{
if (m_selected_glyph == glyph)
if (m_active_glyph == glyph)
return;
m_selected_glyph = glyph;
if (on_glyph_selected)
on_glyph_selected(glyph);
m_active_glyph = glyph;
if (should_reset_selection == ShouldResetSelection::Yes) {
m_selection.set_start(glyph);
m_selection.set_size(1);
}
if (on_active_glyph_changed)
on_active_glyph_changed(glyph);
update();
}
@ -99,7 +136,7 @@ void GlyphMapWidget::paint_event(GUI::PaintEvent& event)
outer_rect.y() + m_vertical_spacing / 2,
font().max_glyph_width(),
font().glyph_height());
if (glyph == m_selected_glyph) {
if (m_selection.contains(glyph)) {
painter.fill_rect(outer_rect, is_focused() ? palette().selection() : palette().inactive_selection());
if (m_font->contains_raw_glyph(glyph))
painter.draw_glyph(inner_rect.location(), glyph, is_focused() ? palette().selection_text() : palette().inactive_selection_text());
@ -113,6 +150,7 @@ void GlyphMapWidget::paint_event(GUI::PaintEvent& event)
painter.draw_emoji(inner_rect.location(), *emoji, *m_font);
}
}
painter.draw_focus_rect(get_outer_rect(m_active_glyph), Gfx::Color::Black);
}
void GlyphMapWidget::mousedown_event(GUI::MouseEvent& event)
@ -125,7 +163,13 @@ void GlyphMapWidget::mousedown_event(GUI::MouseEvent& event)
auto row = (map_position.y() - 1) / ((font().glyph_height() + m_vertical_spacing));
auto glyph = row * columns() + col;
if (row >= 0 && row < rows() && col >= 0 && col < columns() && glyph < m_glyph_count) {
set_selected_glyph(glyph);
if (event.shift())
m_selection.extend_to(glyph);
else {
m_selection.set_size(1);
m_selection.set_start(glyph);
}
set_active_glyph(glyph, ShouldResetSelection::No);
}
}
@ -133,53 +177,76 @@ void GlyphMapWidget::keydown_event(GUI::KeyEvent& event)
{
GUI::Frame::keydown_event(event);
if (!event.ctrl() && !event.shift()) {
m_selection.set_size(1);
m_selection.set_start(m_active_glyph);
}
if (event.key() == KeyCode::Key_Up) {
if (selected_glyph() >= m_columns) {
set_selected_glyph(selected_glyph() - m_columns);
scroll_to_glyph(selected_glyph());
if (m_selection.start() >= m_columns) {
if (event.shift())
m_selection.resize_by(-m_columns);
else
m_selection.set_start(m_selection.start() - m_columns);
set_active_glyph(m_active_glyph - m_columns, ShouldResetSelection::No);
scroll_to_glyph(m_active_glyph);
return;
}
}
if (event.key() == KeyCode::Key_Down) {
if (selected_glyph() < m_glyph_count - m_columns) {
set_selected_glyph(selected_glyph() + m_columns);
scroll_to_glyph(selected_glyph());
if (m_selection.start() < m_glyph_count - m_columns) {
if (event.shift())
m_selection.resize_by(m_columns);
else
m_selection.set_start(m_selection.start() + m_columns);
set_active_glyph(m_active_glyph + m_columns, ShouldResetSelection::No);
scroll_to_glyph(m_active_glyph);
return;
}
}
if (event.key() == KeyCode::Key_Left) {
if (selected_glyph() > 0) {
set_selected_glyph(selected_glyph() - 1);
scroll_to_glyph(selected_glyph());
if (m_selection.start() > 0) {
if (event.shift())
m_selection.resize_by(-1);
else
m_selection.set_start(m_selection.start() - 1);
set_active_glyph(m_active_glyph - 1, ShouldResetSelection::No);
scroll_to_glyph(m_active_glyph);
return;
}
}
if (event.key() == KeyCode::Key_Right) {
if (selected_glyph() < m_glyph_count - 1) {
set_selected_glyph(selected_glyph() + 1);
scroll_to_glyph(selected_glyph());
if (m_selection.start() < m_glyph_count - 1) {
if (event.shift())
m_selection.resize_by(1);
else
m_selection.set_start(m_selection.start() + 1);
set_active_glyph(m_active_glyph + 1, ShouldResetSelection::No);
scroll_to_glyph(m_active_glyph);
return;
}
}
// FIXME: Support selection for these.
if (event.ctrl() && event.key() == KeyCode::Key_Home) {
set_selected_glyph(0);
scroll_to_glyph(selected_glyph());
set_active_glyph(0);
scroll_to_glyph(m_active_glyph);
return;
}
if (event.ctrl() && event.key() == KeyCode::Key_End) {
set_selected_glyph(m_glyph_count - 1);
scroll_to_glyph(selected_glyph());
set_active_glyph(m_glyph_count - 1);
scroll_to_glyph(m_active_glyph);
return;
}
if (!event.ctrl() && event.key() == KeyCode::Key_Home) {
set_selected_glyph(selected_glyph() / m_columns * m_columns);
set_active_glyph(m_active_glyph / m_columns * m_columns);
return;
}
if (!event.ctrl() && event.key() == KeyCode::Key_End) {
int new_selection = selected_glyph() / m_columns * m_columns + (m_columns - 1);
int new_selection = m_active_glyph / m_columns * m_columns + (m_columns - 1);
int max = m_glyph_count - 1;
new_selection = clamp(new_selection, 0, max);
set_selected_glyph(new_selection);
set_active_glyph(new_selection);
return;
}
}

View File

@ -6,7 +6,9 @@
#pragma once
#include <AK/Vector.h>
#include <LibGUI/AbstractScrollableWidget.h>
#include <LibGUI/TextRange.h>
#include <LibGfx/BitmapFont.h>
class GlyphMapWidget final : public GUI::AbstractScrollableWidget {
@ -16,8 +18,40 @@ public:
void initialize(Gfx::BitmapFont&);
int selected_glyph() const { return m_selected_glyph; }
void set_selected_glyph(int);
class Selection {
public:
Selection() = default;
Selection(int start, int size)
: m_start(start)
, m_size(size)
{
}
int size() const { return m_size; }
void set_size(int i) { m_size = i; }
int start() const { return m_start; }
void set_start(int i) { m_start = i; }
Selection normalized() const;
bool contains(int) const;
void resize_by(int i);
void extend_to(int);
private:
int m_start { 0 };
int m_size { 1 };
};
Selection selection() const { return m_selection; }
int active_glyph() const { return m_active_glyph; }
enum class ShouldResetSelection {
Yes,
No
};
void set_active_glyph(int, ShouldResetSelection = ShouldResetSelection::Yes);
void clear_selection() { m_selection.set_size(0); }
void scroll_to_glyph(int);
void update_glyph(int);
@ -27,7 +61,7 @@ public:
Gfx::BitmapFont& font() { return *m_font; }
Gfx::BitmapFont const& font() const { return *m_font; }
Function<void(int)> on_glyph_selected;
Function<void(int)> on_active_glyph_changed;
private:
GlyphMapWidget();
@ -38,12 +72,18 @@ private:
Gfx::IntRect get_outer_rect(int glyph) const;
void cut_glyph(int glyph);
void copy_glyph(int glyph);
void paste_glyph(int glyph);
void delete_glyph(int glyph);
RefPtr<Gfx::BitmapFont> m_font;
int m_glyph_count { 0x110000 };
int m_columns { 0 };
int m_rows { 0 };
int m_horizontal_spacing { 2 };
int m_vertical_spacing { 2 };
int m_selected_glyph { 0 };
Selection m_selection;
int m_active_glyph { 0 };
int m_visible_glyphs { 0 };
};