浏览代码

More work on the variable-width font support.

Katica is now the default system font, and it looks quite nice. :^)
I'm gonna need to refine the GTextBox movement stuff eventually,
but it works well-enough for basic editing now.
Andreas Kling 6 年之前
父节点
当前提交
66a5ddd94a

+ 2 - 2
Applications/Terminal/Terminal.cpp

@@ -19,9 +19,9 @@
 
 Terminal::Terminal(int ptm_fd)
     : m_ptm_fd(ptm_fd)
-    , m_font(Font::default_font())
     , m_notifier(ptm_fd, GNotifier::Read)
 {
+    set_font(Font::default_fixed_width_font());
     m_notifier.on_ready_to_read = [this] (GNotifier& notifier) {
         byte buffer[BUFSIZ];
         ssize_t nread = read(notifier.fd(), buffer, sizeof(buffer));
@@ -798,7 +798,7 @@ void Terminal::force_repaint()
 
 void Terminal::resize_event(GResizeEvent& event)
 {
-    int new_columns = event.size().width() / m_font->glyph_width('x');
+    int new_columns = event.size().width() / font().glyph_width('x');
     int new_rows = event.size().height() / m_line_height;
     set_size(new_columns, new_rows);
 }

+ 0 - 3
Applications/Terminal/Terminal.h

@@ -30,7 +30,6 @@ private:
     virtual void keydown_event(GKeyEvent&) override;
     virtual const char* class_name() const override { return "Terminal"; }
 
-    Font& font() { return *m_font; }
     void scroll_up();
     void newline();
     void set_cursor(unsigned row, unsigned column);
@@ -145,8 +144,6 @@ private:
     bool m_in_active_window { false };
     bool m_need_full_flush { false };
 
-    RetainPtr<Font> m_font;
-
     GNotifier m_notifier;
 
     float m_opacity { 0.8f };

+ 1 - 1
Applications/Terminal/main.cpp

@@ -108,7 +108,7 @@ int main(int argc, char** argv)
     menubar->add_menu(move(app_menu));
 
     auto font_menu = make<GMenu>("Font");
-    GFontDatabase::the().for_each_font([&] (const String& font_name) {
+    GFontDatabase::the().for_each_fixed_width_font([&] (const String& font_name) {
         font_menu->add_action(GAction::create(font_name, [&terminal] (const GAction& action) {
             terminal.set_font(GFontDatabase::the().get_by_name(action.text()));
             terminal.force_repaint();

+ 19 - 6
LibGUI/GFontDatabase.cpp

@@ -24,8 +24,13 @@ GFontDatabase::GFontDatabase()
         if (de->d_name[0] == '.')
             continue;
         auto path = String::format("/res/fonts/%s", de->d_name);
-        if (auto font = Font::load_from_file(path))
-            m_name_to_path.set(font->name(), path);
+        if (auto font = Font::load_from_file(path)) {
+            Metadata metadata;
+            metadata.path = path;
+            metadata.glyph_height = font->glyph_height();
+            metadata.is_fixed_width = font->is_fixed_width();
+            m_name_to_metadata.set(font->name(), move(metadata));
+        }
     }
     closedir(dirp);
 }
@@ -36,15 +41,23 @@ GFontDatabase::~GFontDatabase()
 
 void GFontDatabase::for_each_font(Function<void(const String&)> callback)
 {
-    for (auto& it : m_name_to_path) {
+    for (auto& it : m_name_to_metadata) {
         callback(it.key);
     }
 }
 
+void GFontDatabase::for_each_fixed_width_font(Function<void(const String&)> callback)
+{
+    for (auto& it : m_name_to_metadata) {
+        if (it.value.is_fixed_width)
+            callback(it.key);
+    }
+}
+
 RetainPtr<Font> GFontDatabase::get_by_name(const String& name)
 {
-    auto it = m_name_to_path.find(name);
-    if (it == m_name_to_path.end())
+    auto it = m_name_to_metadata.find(name);
+    if (it == m_name_to_metadata.end())
         return nullptr;
-    return Font::load_from_file((*it).value);
+    return Font::load_from_file((*it).value.path);
 }

+ 8 - 1
LibGUI/GFontDatabase.h

@@ -12,10 +12,17 @@ public:
 
     RetainPtr<Font> get_by_name(const String&);
     void for_each_font(Function<void(const String&)>);
+    void for_each_fixed_width_font(Function<void(const String&)>);
 
 private:
     GFontDatabase();
     ~GFontDatabase();
 
-    HashMap<String, String> m_name_to_path;
+    struct Metadata {
+        String path;
+        bool is_fixed_width;
+        int glyph_height;
+    };
+
+    HashMap<String, Metadata> m_name_to_metadata;
 };

+ 68 - 12
LibGUI/GTextBox.cpp

@@ -21,12 +21,58 @@ void GTextBox::set_text(const String& text)
         return;
     m_text = text;
     m_cursor_position = m_text.length();
+    scroll_cursor_into_view(HorizontalDirection::Right);
     update();
 }
 
+void GTextBox::scroll_cursor_into_view(HorizontalDirection direction)
+{
+    if (visible_content_rect().contains(cursor_content_position()))
+        return;
+    int total_text_width = font().width(m_text);
+    dbgprintf("total_text_width = %d, visible_content width = %d\n", total_text_width, visible_content_rect().width());
+    if (total_text_width < visible_content_rect().width()) {
+        m_scroll_offset = 0;
+        return;
+    }
+    if (direction == HorizontalDirection::Left) {
+        dbgprintf("Left, orig offset = %d\n", m_scroll_offset);
+        m_scroll_offset = cursor_content_position().x();
+        int offset_into_visible = m_scroll_offset - visible_content_rect().x();
+        if (offset_into_visible < font().glyph_width(' '))
+            m_scroll_offset -= width() / 2;
+    } else {
+        m_scroll_offset = cursor_content_position().x() - visible_content_rect().width();
+        dbgprintf("Right, orig offset = %d\n", m_scroll_offset);
+        int offset_into_visible = m_scroll_offset - visible_content_rect().x();
+        if (offset_into_visible > width() / 4) {
+            dbgprintf("Right, adjust offset = %d\n", m_scroll_offset);
+            m_scroll_offset += width() / 2;
+        }
+    }
+    if (m_scroll_offset < 0)
+        m_scroll_offset = 0;
+    if (m_scroll_offset > total_text_width)dbgprintf("Right, adjust offset = %d\n", m_scroll_offset);
+        m_scroll_offset = total_text_width - width();
+}
+
+Rect GTextBox::visible_content_rect() const
+{
+    if (rect().is_empty())
+        return { };
+    return { m_scroll_offset, 0, rect().shrunken(6, 6).width(), rect().shrunken(6, 6).height() };
+}
+
+Point GTextBox::cursor_content_position() const
+{
+    int x = 0;
+    for (int i = 0; i < m_cursor_position; ++i)
+        x += font().glyph_width(m_text[i]) + font().glyph_spacing();
+    return { x, 0 };
+}
+
 void GTextBox::paint_event(GPaintEvent& event)
 {
-    ASSERT(font().is_fixed_width());
     Painter painter(*this);
     painter.set_clip_rect(event.rect());
 
@@ -39,27 +85,25 @@ void GTextBox::paint_event(GPaintEvent& event)
     Rect inner_rect = rect();
     inner_rect.shrink(6, 6);
 
-    ssize_t max_chars_to_paint = inner_rect.width() / font().min_glyph_width();
+    painter.set_clip_rect(inner_rect);
+    painter.translate(-m_scroll_offset, 0);
 
-    int first_visible_char = max((int)m_cursor_position - (int)max_chars_to_paint, 0);
-    ssize_t chars_to_paint = min(m_text.length() - first_visible_char, max_chars_to_paint);
     int y = inner_rect.center().y() - font().glyph_height() / 2;    
-    int space_width = font().glyph_width(' ');
+    int space_width = font().glyph_width(' ') + font().glyph_spacing();
     int x = inner_rect.x();
 
-    for (ssize_t i = 0; i < chars_to_paint; ++i) {
-        char ch = m_text[first_visible_char + i];
+    for (int i = 0; i < m_text.length(); ++i) {
+        char ch = m_text[i];
         if (ch == ' ') {
             x += space_width;
             continue;
         }
         painter.draw_glyph({x, y}, ch, Color::Black);
-        x += font().glyph_width(ch);
+        x += font().glyph_width(ch) + font().glyph_spacing();
     }
 
     if (is_focused() && m_cursor_blink_state) {
-        int visible_cursor_position = m_cursor_position - first_visible_char;
-        Rect cursor_rect(inner_rect.x() + visible_cursor_position * font().glyph_width('x'), inner_rect.y(), 1, inner_rect.height());
+        Rect cursor_rect(inner_rect.x() + cursor_content_position().x(), inner_rect.y(), 1, inner_rect.height());
         painter.fill_rect(cursor_rect, foreground_color());
     }
 }
@@ -76,6 +120,7 @@ void GTextBox::handle_backspace()
     if (m_text.length() == 1) {
         m_text = String::empty();
         m_cursor_position = 0;
+        m_scroll_offset = 0;
         if (on_change)
             on_change(*this);
         update();
@@ -90,6 +135,7 @@ void GTextBox::handle_backspace()
 
     m_text = move(new_text);
     --m_cursor_position;
+    scroll_cursor_into_view(HorizontalDirection::Left);
     if (on_change)
         on_change(*this);
     update();
@@ -99,14 +145,18 @@ void GTextBox::keydown_event(GKeyEvent& event)
 {
     switch (event.key()) {
     case KeyCode::Key_Left:
-        if (m_cursor_position)
+        if (m_cursor_position) {
             --m_cursor_position;
+            scroll_cursor_into_view(HorizontalDirection::Left);
+        }
         m_cursor_blink_state = true;
         update();
         return;
     case KeyCode::Key_Right:
-        if (m_cursor_position < m_text.length())
+        if (m_cursor_position < m_text.length()) {
             ++m_cursor_position;
+            scroll_cursor_into_view(HorizontalDirection::Right);
+        }
         m_cursor_blink_state = true;
         update();
         return;
@@ -130,6 +180,7 @@ void GTextBox::keydown_event(GKeyEvent& event)
 
         m_text = move(new_text);
         ++m_cursor_position;
+        scroll_cursor_into_view(HorizontalDirection::Right);
         if (on_change)
             on_change(*this);
         update();
@@ -156,3 +207,8 @@ void GTextBox::focusout_event(GEvent&)
 {
     stop_timer();
 }
+
+void GTextBox::resize_event(GResizeEvent&)
+{
+    scroll_cursor_into_view(HorizontalDirection::Right);
+}

+ 5 - 0
LibGUI/GTextBox.h

@@ -22,12 +22,17 @@ private:
     virtual void timer_event(GTimerEvent&) override;
     virtual void focusin_event(GEvent&) override;
     virtual void focusout_event(GEvent&) override;
+    virtual void resize_event(GResizeEvent&) override;
     virtual bool accepts_focus() const override { return true; }
 
+    Point cursor_content_position() const;
+    Rect visible_content_rect() const;
     void handle_backspace();
+    void scroll_cursor_into_view(HorizontalDirection);
 
     String m_text;
     int m_cursor_position { 0 };
+    int m_scroll_offset { 0 };
     bool m_cursor_blink_state { false };
 };
 

+ 2 - 0
LibGUI/GWidget.h

@@ -14,6 +14,8 @@ class GWindow;
 
 enum class SizePolicy { Fixed, Fill };
 enum class Orientation { Horizontal, Vertical };
+enum class HorizontalDirection { Left, Right };
+enum class VerticalDirection { Up, Down };
 
 class GWidget : public GObject {
 public:

+ 14 - 4
SharedGraphics/Font.cpp

@@ -9,9 +9,6 @@
 #include <LibC/errno.h>
 #include <LibC/mman.h>
 
-static Font* s_default_font;
-static Font* s_default_bold_font;
-
 struct [[gnu::packed]] FontFileHeader {
     char magic[4];
     byte glyph_width;
@@ -24,7 +21,8 @@ struct [[gnu::packed]] FontFileHeader {
 
 Font& Font::default_font()
 {
-    static const char* default_font_path = "/res/fonts/CsillaThin7x10.font";
+    static Font* s_default_font;
+    static const char* default_font_path = "/res/fonts/Katica10.font";
     if (!s_default_font) {
         s_default_font = Font::load_from_file(default_font_path).leak_ref();
         ASSERT(s_default_font);
@@ -32,8 +30,20 @@ Font& Font::default_font()
     return *s_default_font;
 }
 
+Font& Font::default_fixed_width_font()
+{
+    static Font* s_default_fixed_width_font;
+    static const char* default_fixed_width_font_path = "/res/fonts/CsillaThin7x10.font";
+    if (!s_default_fixed_width_font) {
+        s_default_fixed_width_font = Font::load_from_file(default_fixed_width_font_path).leak_ref();
+        ASSERT(s_default_fixed_width_font);
+    }
+    return *s_default_fixed_width_font;
+}
+
 Font& Font::default_bold_font()
 {
+    static Font* s_default_bold_font;
     static const char* default_bold_font_path = "/res/fonts/CsillaBold7x10.font";
     if (!s_default_bold_font) {
         s_default_bold_font = Font::load_from_file(default_bold_font_path).leak_ref();

+ 2 - 0
SharedGraphics/Font.h

@@ -43,6 +43,8 @@ public:
     static Font& default_font();
     static Font& default_bold_font();
 
+    static Font& default_fixed_width_font();
+
     RetainPtr<Font> clone() const;
 
     static RetainPtr<Font> load_from_memory(const byte*);

+ 1 - 0
Userland/guitest2.cpp

@@ -99,6 +99,7 @@ GWindow* make_launcher_window()
 
     auto* other_textbox = new GTextBox(widget);
     other_textbox->set_relative_rect({ 5, 140, 90, 20 });
+    other_textbox->set_text("Hello there I am text.");
 
     auto* checkbox = new GCheckBox(widget);
     checkbox->set_relative_rect({ 5, 170, 90, 20 });

+ 3 - 1
WindowServer/WSWindowManager.cpp

@@ -359,11 +359,13 @@ void WSWindowManager::set_current_menubar(WSMenuBar* menubar)
     dbgprintf("[WM] Current menubar is now %p\n", menubar);
 #endif
     Point next_menu_location { menubar_menu_margin() / 2, 0 };
+    int index = 0;
     for_each_active_menubar_menu([&] (WSMenu& menu) {
-        int text_width = font().width(menu.name());
+        int text_width = index == 1 ? Font::default_bold_font().width(menu.name()) : font().width(menu.name());
         menu.set_rect_in_menubar({ next_menu_location.x() - menubar_menu_margin() / 2, 0, text_width + menubar_menu_margin(), menubar_rect().height() - 1 });
         menu.set_text_rect_in_menubar({ next_menu_location, { text_width, menubar_rect().height() } });
         next_menu_location.move_by(menu.rect_in_menubar().width(), 0);
+        ++index;
         return true;
     });
     invalidate(menubar_rect());