diff --git a/Applications/Terminal/Terminal.cpp b/Applications/Terminal/Terminal.cpp index 136f47e86d..25df6def7d 100644 --- a/Applications/Terminal/Terminal.cpp +++ b/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); } diff --git a/Applications/Terminal/Terminal.h b/Applications/Terminal/Terminal.h index 0e1a5f2d54..ec11a62f0e 100644 --- a/Applications/Terminal/Terminal.h +++ b/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 m_font; - GNotifier m_notifier; float m_opacity { 0.8f }; diff --git a/Applications/Terminal/main.cpp b/Applications/Terminal/main.cpp index 6bb0482efd..dfbc2fa6fa 100644 --- a/Applications/Terminal/main.cpp +++ b/Applications/Terminal/main.cpp @@ -108,7 +108,7 @@ int main(int argc, char** argv) menubar->add_menu(move(app_menu)); auto font_menu = make("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(); diff --git a/LibGUI/GFontDatabase.cpp b/LibGUI/GFontDatabase.cpp index a07d21ddd1..63c8f7ec4d 100644 --- a/LibGUI/GFontDatabase.cpp +++ b/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 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 callback) +{ + for (auto& it : m_name_to_metadata) { + if (it.value.is_fixed_width) + callback(it.key); + } +} + RetainPtr 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); } diff --git a/LibGUI/GFontDatabase.h b/LibGUI/GFontDatabase.h index 7ed0c4f7e1..5cc37cc243 100644 --- a/LibGUI/GFontDatabase.h +++ b/LibGUI/GFontDatabase.h @@ -12,10 +12,17 @@ public: RetainPtr get_by_name(const String&); void for_each_font(Function); + void for_each_fixed_width_font(Function); private: GFontDatabase(); ~GFontDatabase(); - HashMap m_name_to_path; + struct Metadata { + String path; + bool is_fixed_width; + int glyph_height; + }; + + HashMap m_name_to_metadata; }; diff --git a/LibGUI/GTextBox.cpp b/LibGUI/GTextBox.cpp index dfc46e3ff9..23302d77c1 100644 --- a/LibGUI/GTextBox.cpp +++ b/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); +} diff --git a/LibGUI/GTextBox.h b/LibGUI/GTextBox.h index f195b28030..af511d85f4 100644 --- a/LibGUI/GTextBox.h +++ b/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 }; }; diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index 1d502cdb12..0a287e5988 100644 --- a/LibGUI/GWidget.h +++ b/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: diff --git a/SharedGraphics/Font.cpp b/SharedGraphics/Font.cpp index 4399f0634a..990853a493 100644 --- a/SharedGraphics/Font.cpp +++ b/SharedGraphics/Font.cpp @@ -9,9 +9,6 @@ #include #include -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(); diff --git a/SharedGraphics/Font.h b/SharedGraphics/Font.h index 776e5c1d46..b0b479ac5c 100644 --- a/SharedGraphics/Font.h +++ b/SharedGraphics/Font.h @@ -43,6 +43,8 @@ public: static Font& default_font(); static Font& default_bold_font(); + static Font& default_fixed_width_font(); + RetainPtr clone() const; static RetainPtr load_from_memory(const byte*); diff --git a/Userland/guitest2.cpp b/Userland/guitest2.cpp index 06e672ef1e..f91cf451e7 100644 --- a/Userland/guitest2.cpp +++ b/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 }); diff --git a/WindowServer/WSWindowManager.cpp b/WindowServer/WSWindowManager.cpp index 10859ff5e2..f0398b87f7 100644 --- a/WindowServer/WSWindowManager.cpp +++ b/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());