forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start working on a simple graphical font editor.
Editing fonts by editing text files is really slow and boring. A simple font editor seems like a good way to take LibGUI for a spin.
- Loading branch information
1 parent
5e0b7f1
commit 6fc3c38
Showing
18 changed files
with
390 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.o | ||
*.d | ||
FontEditor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
#include "FontEditor.h" | ||
#include <SharedGraphics/Painter.h> | ||
#include <LibGUI/GLabel.h> | ||
|
||
FontEditorWidget::FontEditorWidget(GWidget* parent) | ||
: GWidget(parent) | ||
{ | ||
auto mutable_font = Font::default_font().clone(); | ||
|
||
m_glyph_map_widget = new GlyphMapWidget(*mutable_font, this); | ||
m_glyph_map_widget->move_to({ 90, 5 }); | ||
|
||
m_glyph_editor_widget = new GlyphEditorWidget(*mutable_font, this); | ||
m_glyph_editor_widget->move_to({ 5, 5 }); | ||
|
||
auto* label = new GLabel(this); | ||
label->set_relative_rect({ 5, 110, 140, 20 }); | ||
|
||
m_glyph_editor_widget->on_glyph_altered = [this] { | ||
m_glyph_map_widget->update(); | ||
}; | ||
|
||
m_glyph_map_widget->on_glyph_selected = [this, label] (byte glyph) { | ||
m_glyph_editor_widget->set_glyph(glyph); | ||
label->set_text(String::format("Glyph: 0x%b (%c)", glyph, glyph)); | ||
}; | ||
|
||
m_glyph_map_widget->set_selected_glyph('A'); | ||
} | ||
|
||
FontEditorWidget::~FontEditorWidget() | ||
{ | ||
} | ||
|
||
GlyphMapWidget::GlyphMapWidget(Font& mutable_font, GWidget* parent) | ||
: GWidget(parent) | ||
, m_font(mutable_font) | ||
{ | ||
set_relative_rect({ 0, 0, preferred_width(), preferred_height() }); | ||
} | ||
|
||
GlyphMapWidget::~GlyphMapWidget() | ||
{ | ||
} | ||
|
||
int GlyphMapWidget::preferred_width() const | ||
{ | ||
return columns() * (font().glyph_width() + m_horizontal_spacing); | ||
} | ||
|
||
int GlyphMapWidget::preferred_height() const | ||
{ | ||
return rows() * (font().glyph_height() + m_vertical_spacing); | ||
} | ||
|
||
void GlyphMapWidget::set_selected_glyph(byte glyph) | ||
{ | ||
if (m_selected_glyph == glyph) | ||
return; | ||
m_selected_glyph = glyph; | ||
if (on_glyph_selected) | ||
on_glyph_selected(glyph); | ||
update(); | ||
} | ||
|
||
Rect GlyphMapWidget::get_outer_rect(byte glyph) const | ||
{ | ||
int row = glyph / columns(); | ||
int column = glyph % columns(); | ||
return { | ||
column * (font().glyph_width() + m_horizontal_spacing), | ||
row * (font().glyph_height() + m_vertical_spacing), | ||
font().glyph_width() + m_horizontal_spacing, | ||
font().glyph_height() + m_horizontal_spacing | ||
}; | ||
} | ||
|
||
void GlyphMapWidget::paint_event(GPaintEvent&) | ||
{ | ||
Painter painter(*this); | ||
painter.set_font(font()); | ||
painter.fill_rect(rect(), Color::White); | ||
|
||
byte glyph = 0; | ||
|
||
for (int row = 0; row < rows(); ++row) { | ||
for (int column = 0; column < columns(); ++column, ++glyph) { | ||
Rect outer_rect = get_outer_rect(glyph); | ||
Rect inner_rect( | ||
outer_rect.x() + m_horizontal_spacing / 2, | ||
outer_rect.y() + m_vertical_spacing / 2, | ||
font().glyph_width(), | ||
font().glyph_height() | ||
); | ||
if (glyph == m_selected_glyph) { | ||
painter.fill_rect(outer_rect, Color::Red); | ||
painter.draw_glyph(inner_rect.location(), glyph, Color::White); | ||
} else { | ||
painter.draw_glyph(inner_rect.location(), glyph, Color::Black); | ||
} | ||
} | ||
} | ||
} | ||
|
||
void GlyphMapWidget::mousedown_event(GMouseEvent& event) | ||
{ | ||
// FIXME: This is a silly loop. | ||
for (unsigned glyph = 0; glyph < 256; ++glyph) { | ||
if (get_outer_rect(glyph).contains(event.position())) { | ||
set_selected_glyph(glyph); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
GlyphEditorWidget::GlyphEditorWidget(Font& mutable_font, GWidget* parent) | ||
: GWidget(parent) | ||
, m_font(mutable_font) | ||
{ | ||
set_relative_rect({ 0, 0, preferred_width(), preferred_height() }); | ||
} | ||
|
||
GlyphEditorWidget::~GlyphEditorWidget() | ||
{ | ||
} | ||
|
||
void GlyphEditorWidget::set_glyph(byte glyph) | ||
{ | ||
if (m_glyph == glyph) | ||
return; | ||
m_glyph = glyph; | ||
update(); | ||
} | ||
|
||
void GlyphEditorWidget::paint_event(GPaintEvent&) | ||
{ | ||
Painter painter(*this); | ||
painter.fill_rect(rect(), Color::White); | ||
painter.draw_rect(rect(), Color::Black); | ||
|
||
auto& bitmap = font().glyph_bitmap(m_glyph); | ||
|
||
for (int y = 0; y < font().glyph_height(); ++y) { | ||
for (int x = 0; x < font().glyph_width(); ++x) { | ||
Rect rect { x * m_scale, y * m_scale, m_scale, m_scale }; | ||
painter.fill_rect(rect, bitmap.bit_at(x, y) ? Color::Black : Color::White); | ||
} | ||
} | ||
} | ||
|
||
void GlyphEditorWidget::mousedown_event(GMouseEvent& event) | ||
{ | ||
draw_at_mouse(event); | ||
} | ||
|
||
void GlyphEditorWidget::mousemove_event(GMouseEvent& event) | ||
{ | ||
if (event.buttons() & (GMouseButton::Left | GMouseButton::Right)) | ||
draw_at_mouse(event); | ||
} | ||
|
||
void GlyphEditorWidget::draw_at_mouse(const GMouseEvent& event) | ||
{ | ||
bool set = event.buttons() & GMouseButton::Left; | ||
bool unset = event.buttons() & GMouseButton::Right; | ||
if (!(set ^ unset)) | ||
return; | ||
byte new_bit = set ? '#' : ' '; | ||
int x = event.x() / m_scale; | ||
int y = event.y() / m_scale; | ||
auto& bitmap = font().glyph_bitmap(m_glyph); | ||
auto* mutable_bits = const_cast<char*>(bitmap.bits()); | ||
ASSERT((unsigned)x < bitmap.width()); | ||
ASSERT((unsigned)y < bitmap.height()); | ||
auto& bit = mutable_bits[y * bitmap.width() + x]; | ||
if (bit == new_bit) | ||
return; | ||
bit = new_bit; | ||
if (on_glyph_altered) | ||
on_glyph_altered(); | ||
update(); | ||
} | ||
|
||
int GlyphEditorWidget::preferred_width() const | ||
{ | ||
return font().glyph_width() * m_scale; | ||
} | ||
|
||
int GlyphEditorWidget::preferred_height() const | ||
{ | ||
return font().glyph_height() * m_scale; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#pragma once | ||
|
||
#include <LibGUI/GWidget.h> | ||
#include <AK/Function.h> | ||
|
||
class GlyphEditorWidget; | ||
class GlyphMapWidget; | ||
|
||
class FontEditorWidget final : public GWidget { | ||
public: | ||
FontEditorWidget(GWidget* parent = nullptr); | ||
virtual ~FontEditorWidget() override; | ||
|
||
private: | ||
GlyphMapWidget* m_glyph_map_widget { nullptr }; | ||
GlyphEditorWidget* m_glyph_editor_widget { nullptr }; | ||
}; | ||
|
||
class GlyphMapWidget final : public GWidget { | ||
public: | ||
GlyphMapWidget(Font&, GWidget* parent); | ||
virtual ~GlyphMapWidget() override; | ||
|
||
byte selected_glyph() const { return m_selected_glyph; } | ||
void set_selected_glyph(byte); | ||
|
||
int rows() const { return m_rows; } | ||
int columns() const { return 256 / m_rows; } | ||
|
||
int preferred_width() const; | ||
int preferred_height() const; | ||
|
||
Font& font() { return *m_font; } | ||
const Font& font() const { return *m_font; } | ||
|
||
Function<void(byte)> on_glyph_selected; | ||
|
||
private: | ||
virtual void paint_event(GPaintEvent&) override; | ||
virtual void mousedown_event(GMouseEvent&) override; | ||
|
||
Rect get_outer_rect(byte glyph) const; | ||
|
||
RetainPtr<Font> m_font; | ||
int m_rows { 8 }; | ||
int m_horizontal_spacing { 2 }; | ||
int m_vertical_spacing { 2 }; | ||
byte m_selected_glyph { 0 }; | ||
}; | ||
|
||
class GlyphEditorWidget final : public GWidget { | ||
public: | ||
GlyphEditorWidget(Font&, GWidget* parent); | ||
virtual ~GlyphEditorWidget() override; | ||
|
||
byte glyph() const { return m_glyph; } | ||
void set_glyph(byte); | ||
|
||
int preferred_width() const; | ||
int preferred_height() const; | ||
|
||
Font& font() { return *m_font; } | ||
const Font& font() const { return *m_font; } | ||
|
||
Function<void()> on_glyph_altered; | ||
|
||
private: | ||
virtual void paint_event(GPaintEvent&) override; | ||
virtual void mousedown_event(GMouseEvent&) override; | ||
virtual void mousemove_event(GMouseEvent&) override; | ||
|
||
void draw_at_mouse(const GMouseEvent&); | ||
|
||
RetainPtr<Font> m_font; | ||
byte m_glyph { 0 }; | ||
int m_scale { 10 }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
OBJS = \ | ||
FontEditor.o \ | ||
main.o | ||
|
||
APP = FontEditor | ||
|
||
ARCH_FLAGS = | ||
STANDARD_FLAGS = -std=c++17 -nostdinc++ -nostdlib -nostdinc | ||
USERLAND_FLAGS = -ffreestanding -fno-stack-protector -fno-ident | ||
WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings | ||
FLAVOR_FLAGS = -march=i386 -mregparm=3 -m32 -fno-exceptions -fno-rtti -fmerge-all-constants -fno-unroll-loops -fno-pie -fno-pic | ||
OPTIMIZATION_FLAGS = -Oz -fno-asynchronous-unwind-tables | ||
INCLUDE_FLAGS = -I.. -I. -I../LibC | ||
|
||
DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND | ||
|
||
CXXFLAGS = -MMD -MP $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(USERLAND_FLAGS) $(FLAVOR_FLAGS) $(ARCH_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES) | ||
CXX = clang | ||
LD = ld | ||
AR = ar | ||
LDFLAGS = -static --strip-debug -melf_i386 --build-id=none -z norelro -z now -e _start --gc-sections | ||
|
||
all: $(APP) | ||
|
||
$(APP): $(OBJS) | ||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a | ||
|
||
.cpp.o: | ||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< | ||
|
||
-include $(OBJS:%.o=%.d) | ||
|
||
clean: | ||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#include "FontEditor.h" | ||
#include <LibGUI/GEventLoop.h> | ||
#include <LibGUI/GWindow.h> | ||
#include <stdio.h> | ||
|
||
int main(int argc, char** argv) | ||
{ | ||
(void) argc; | ||
(void) argv; | ||
|
||
GEventLoop loop; | ||
auto* window = new GWindow; | ||
window->set_title("FontEditor"); | ||
window->set_rect({ 50, 50, 420, 200 }); | ||
auto* font_editor = new FontEditorWidget; | ||
font_editor->set_relative_rect({ 0, 0, 420, 200 }); | ||
window->set_main_widget(font_editor); | ||
window->show(); | ||
return loop.exec(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#!/bin/bash | ||
|
||
sudo id | ||
|
||
make -C ../FontEditor clean && \ | ||
make -C ../FontEditor && \ | ||
sudo ./sync.sh | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.