mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 16:12:44 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									e53cef02d5
								
							
						
					
					
						commit
						66a5ddd94a
					
				
					 12 changed files with 125 additions and 30 deletions
				
			
		|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -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 }; | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| }; | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
|  | @ -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 }; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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*); | ||||
|  |  | |||
|  | @ -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 }); | ||||
|  |  | |||
|  | @ -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()); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling