diff --git a/Kernel/Process.h b/Kernel/Process.h index eddd71ba29..865a6b5684 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -195,7 +195,7 @@ public: int gui$create_window(const GUI_CreateWindowParameters*); int gui$destroy_window(int window_id); int gui$get_window_backing_store(int window_id, GUI_WindowBackingStoreInfo*); - int gui$invalidate_window(int window_id); + int gui$invalidate_window(int window_id, const GUI_Rect*); DisplayInfo get_display_info(); diff --git a/Kernel/ProcessGUI.cpp b/Kernel/ProcessGUI.cpp index bdd0636ba1..417dac360e 100644 --- a/Kernel/ProcessGUI.cpp +++ b/Kernel/ProcessGUI.cpp @@ -99,16 +99,21 @@ int Process::gui$get_window_backing_store(int window_id, GUI_WindowBackingStoreI return 0; } -int Process::gui$invalidate_window(int window_id) +int Process::gui$invalidate_window(int window_id, const GUI_Rect* rect) { dbgprintf("%s<%u> gui$invalidate_window (window_id=%d)\n", name().characters(), pid(), window_id); if (window_id < 0) return -EINVAL; + if (rect && !validate_read_typed(rect)) + return -EFAULT; auto it = m_windows.find(window_id); if (it == m_windows.end()) return -EBADWINDOW; auto& window = *(*it).value; - WSEventLoop::the().post_event(&window, make(WSEvent::WM_Invalidate)); + auto event = make(WSEvent::WM_Invalidate); + if (rect) + event->set_rect(*rect); + WSEventLoop::the().post_event(&window, move(event)); WSEventLoop::the().server_process().request_wakeup(); return 0; } diff --git a/Kernel/Syscall.cpp b/Kernel/Syscall.cpp index a4d9c0e9de..038ebc4318 100644 --- a/Kernel/Syscall.cpp +++ b/Kernel/Syscall.cpp @@ -198,7 +198,7 @@ static dword handle(RegisterDump& regs, dword function, dword arg1, dword arg2, case Syscall::SC_gui_get_window_backing_store: return current->gui$get_window_backing_store((int)arg1, (GUI_WindowBackingStoreInfo*)arg2); case Syscall::SC_gui_invalidate_window: - return current->gui$invalidate_window((int)arg1); + return current->gui$invalidate_window((int)arg1, (const GUI_Rect*)arg2); default: kprintf("<%u> int0x80: Unknown function %u requested {%x, %x, %x}\n", current->pid(), function, arg1, arg2, arg3); break; diff --git a/LibC/gui.cpp b/LibC/gui.cpp index fd4b75dfc4..2c11bcb8c8 100644 --- a/LibC/gui.cpp +++ b/LibC/gui.cpp @@ -9,9 +9,9 @@ int gui_create_window(const GUI_CreateWindowParameters* params) __RETURN_WITH_ERRNO(rc, rc, -1); } -int gui_invalidate_window(int window_id) +int gui_invalidate_window(int window_id, const GUI_Rect* rect) { - int rc = syscall(SC_gui_invalidate_window, window_id); + int rc = syscall(SC_gui_invalidate_window, window_id, rect); __RETURN_WITH_ERRNO(rc, rc, -1); } diff --git a/LibC/gui.h b/LibC/gui.h index fe7f1134b5..e92809fe4a 100644 --- a/LibC/gui.h +++ b/LibC/gui.h @@ -6,7 +6,7 @@ __BEGIN_DECLS int gui_create_window(const GUI_CreateWindowParameters* params); -int gui_invalidate_window(int window_id); +int gui_invalidate_window(int window_id, const GUI_Rect*); int gui_get_window_backing_store(int window_id, GUI_WindowBackingStoreInfo* info); __END_DECLS diff --git a/Terminal/Terminal.cpp b/Terminal/Terminal.cpp index 0b893f05a6..8c2aeab0d7 100644 --- a/Terminal/Terminal.cpp +++ b/Terminal/Terminal.cpp @@ -49,6 +49,7 @@ Terminal::Terminal() // Rightmost column is always last tab on line. m_horizontal_tabs[columns() - 1] = 1; + m_row_needs_invalidation = (bool*)(malloc(rows() * sizeof(bool))); m_buffer = (byte*)malloc(rows() * columns()); m_attributes = (Attribute*)malloc(rows() * columns() * sizeof(Attribute)); memset(m_buffer, ' ', m_rows * m_columns); @@ -58,6 +59,7 @@ Terminal::Terminal() Terminal::~Terminal() { + free(m_row_needs_invalidation); free(m_buffer); free(m_attributes); free(m_horizontal_tabs); @@ -408,6 +410,12 @@ Rect Terminal::glyph_rect(word row, word column) return { x + m_inset, y + m_inset, font().glyph_width(), font().glyph_height() }; } +Rect Terminal::row_rect(word row) +{ + int y = row * m_line_height; + return { m_inset, y + m_inset, font().glyph_width() * m_columns, font().glyph_height() }; +} + inline Terminal::Attribute& Terminal::attribute_at(word row, word column) { return m_attributes[(row * m_columns) + column]; @@ -417,6 +425,10 @@ void Terminal::paint() { Rect rect { 0, 0, m_pixel_width, m_pixel_height }; Painter painter(*m_backing); + + bool need_full_invalidation = false; + memset(m_row_needs_invalidation, 0, rows() * sizeof(bool)); + #ifdef FAST_SCROLL if (m_rows_to_scroll_backing_store && m_rows_to_scroll_backing_store < m_rows) { int first_scanline = m_inset; @@ -428,6 +440,7 @@ void Terminal::paint() m_backing->scanline(second_scanline), scanlines_to_copy * m_pixel_width ); + need_full_invalidation = true; attribute_at(m_cursor_row - m_rows_to_scroll_backing_store, m_cursor_column).dirty = true; } m_rows_to_scroll_backing_store = 0; @@ -439,6 +452,7 @@ void Terminal::paint() if (!attribute.dirty) continue; attribute.dirty = false; + m_row_needs_invalidation[row] = true; char ch = m_buffer[(row * m_columns) + (column)]; auto character_rect = glyph_rect(row, column); auto character_background = ansi_color(attribute.background_color); @@ -455,10 +469,30 @@ void Terminal::paint() else painter.draw_rect(cursor_rect, Color::MidGray); - if (m_belling) - painter.draw_rect(rect, Color::Red); + m_row_needs_invalidation[m_cursor_row] = true; - int rc = gui_invalidate_window(m_window_id); + if (m_belling) { + need_full_invalidation = true; + painter.draw_rect(rect, Color::Red); + } + + if (need_full_invalidation) { + invalidate_window(); + return; + } + + Rect invalidation_rect; + for (int i = 0; i < m_rows; ++i) { + if (m_row_needs_invalidation[i]) + invalidation_rect = invalidation_rect.united(row_rect(i)); + } + invalidate_window(invalidation_rect); +} + +void Terminal::invalidate_window(const Rect& a_rect) +{ + GUI_Rect rect = a_rect; + int rc = gui_invalidate_window(m_window_id, a_rect.is_null() ? nullptr : &rect); if (rc < 0) { perror("gui_invalidate_window"); exit(1); diff --git a/Terminal/Terminal.h b/Terminal/Terminal.h index 4b069b141b..50b6ecf03f 100644 --- a/Terminal/Terminal.h +++ b/Terminal/Terminal.h @@ -25,6 +25,7 @@ private: void set_cursor(unsigned row, unsigned column); void put_character_at(unsigned row, unsigned column, byte ch); void invalidate_cursor(); + void invalidate_window(const Rect& = Rect()); void escape$A(const Vector&); void escape$D(const Vector&); @@ -40,6 +41,7 @@ private: word columns() const { return m_columns; } word rows() const { return m_rows; } Rect glyph_rect(word row, word column); + Rect row_rect(word row); struct Attribute { Attribute() { reset(); } @@ -58,6 +60,7 @@ private: byte* m_buffer { nullptr }; Attribute* m_attributes { nullptr }; + bool* m_row_needs_invalidation { nullptr }; word m_columns { 0 }; word m_rows { 0 }; diff --git a/Userland/guitest.cpp b/Userland/guitest.cpp index e538fbf09a..6b2caf0b7c 100644 --- a/Userland/guitest.cpp +++ b/Userland/guitest.cpp @@ -43,7 +43,7 @@ int main(int argc, char** argv) paint(*bitmap, backing.size.width, backing.size.height); - rc = gui_invalidate_window(window_id); + rc = gui_invalidate_window(window_id, nullptr); if (rc < 0) { perror("gui_invalidate_window"); return 1; @@ -69,7 +69,7 @@ int main(int argc, char** argv) if (event.type == GUI_Event::Type::MouseDown) { paint(*bitmap, backing.size.width, backing.size.height); - gui_invalidate_window(window_id); + gui_invalidate_window(window_id, nullptr); } } diff --git a/Widgets/Rect.cpp b/Widgets/Rect.cpp index f07d78e7fe..5b01bf2e1e 100644 --- a/Widgets/Rect.cpp +++ b/Widgets/Rect.cpp @@ -22,6 +22,10 @@ void Rect::intersect(const Rect& other) Rect Rect::united(const Rect& other) const { + if (is_null()) + return other; + if (other.is_null()) + return *this; Rect rect; rect.set_left(min(left(), other.left())); rect.set_top(min(top(), other.top())); diff --git a/Widgets/Rect.h b/Widgets/Rect.h index aa9b02b536..e99e6d6ded 100644 --- a/Widgets/Rect.h +++ b/Widgets/Rect.h @@ -20,9 +20,14 @@ public: } Rect(const GUI_Rect&); + bool is_null() const + { + return width() == 0 && height() == 0; + } + bool is_empty() const { - return width() == 0 || height() == 0; + return width() <= 0 || height() <= 0; } void move_by(int dx, int dy) diff --git a/WindowServer/WSEvent.h b/WindowServer/WSEvent.h index 702cd32efd..8a3a4ce99d 100644 --- a/WindowServer/WSEvent.h +++ b/WindowServer/WSEvent.h @@ -53,8 +53,15 @@ public: bool isKeyEvent() const { return m_type == KeyUp || m_type == KeyDown; } bool isPaintEvent() const { return m_type == Paint; } + Rect rect() const { return m_rect; } + void set_rect(const GUI_Rect& rect) + { + m_rect = rect; + } + private: Type m_type { Invalid }; + Rect m_rect; }; class PaintEvent final : public WSEvent { diff --git a/WindowServer/WSEventLoop.cpp b/WindowServer/WSEventLoop.cpp index 37d77fc0b9..7fed463269 100644 --- a/WindowServer/WSEventLoop.cpp +++ b/WindowServer/WSEventLoop.cpp @@ -83,7 +83,9 @@ void WSEventLoop::post_event(WSEventReceiver* receiver, OwnPtr&& event) if (event->type() == WSEvent::WM_Invalidate) { for (auto& queued_event : m_queued_events) { - if (receiver == queued_event.receiver && queued_event.event->type() == WSEvent::WM_Invalidate) { + if (receiver == queued_event.receiver + && queued_event.event->type() == WSEvent::WM_Invalidate + && (queued_event.event->rect().is_empty() || queued_event.event->rect().contains(event->rect()))) { #ifdef WSEVENTLOOP_DEBUG dbgprintf("Swallow WM_Invalidate\n"); #endif diff --git a/WindowServer/WSWindow.cpp b/WindowServer/WSWindow.cpp index b3e986c301..d18d273d9c 100644 --- a/WindowServer/WSWindow.cpp +++ b/WindowServer/WSWindow.cpp @@ -75,7 +75,7 @@ void WSWindow::event(WSEvent& event) gui_event.key.character = static_cast(event).text()[0]; break; case WSEvent::WM_Invalidate: - WSWindowManager::the().invalidate(*this); + WSWindowManager::the().invalidate(*this, event.rect()); return; case WSEvent::WindowActivated: gui_event.type = GUI_Event::Type::WindowActivated; diff --git a/WindowServer/WSWindowManager.cpp b/WindowServer/WSWindowManager.cpp index 31e8cad249..325863802f 100644 --- a/WindowServer/WSWindowManager.cpp +++ b/WindowServer/WSWindowManager.cpp @@ -412,6 +412,21 @@ void WSWindowManager::invalidate(const WSWindow& window) invalidate(outerRectForWindow(window.rect())); } +void WSWindowManager::invalidate(const WSWindow& window, const Rect& rect) +{ + if (rect.is_empty()) { + invalidate(window); + return; + } + ASSERT_INTERRUPTS_ENABLED(); + LOCKER(m_lock); + auto outer_rect = outerRectForWindow(window.rect()); + auto inner_rect = rect; + inner_rect.move_by(window.position()); + inner_rect.intersect(outer_rect); + invalidate(inner_rect); +} + void WSWindowManager::flush(const Rect& a_rect) { auto rect = Rect::intersection(a_rect, m_screen_rect); diff --git a/WindowServer/WSWindowManager.h b/WindowServer/WSWindowManager.h index 4a7d26d246..180a44abe0 100644 --- a/WindowServer/WSWindowManager.h +++ b/WindowServer/WSWindowManager.h @@ -34,6 +34,7 @@ public: void draw_cursor(); void invalidate(const WSWindow&); + void invalidate(const WSWindow&, const Rect&); void invalidate(const Rect&); void invalidate(); void flush(const Rect&);