diff --git a/include/kernel/framebuffer.h b/include/kernel/framebuffer.h index c25a69c..c795781 100644 --- a/include/kernel/framebuffer.h +++ b/include/kernel/framebuffer.h @@ -7,6 +7,12 @@ namespace framebuffer { +enum class BufferTarget +{ + Draw, + Display +}; + struct FrameBufferInfo { uint32_t width = 0; @@ -22,9 +28,15 @@ const FrameBufferInfo &info(); uint32_t framebuffer_physical_address(); uint32_t framebuffer_size(); +bool double_buffering_enabled(); uint32_t pack_color(uint8_t r, uint8_t g, uint8_t b); -void fill_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint32_t color); +void fill_rect(uint32_t x, + uint32_t y, + uint32_t width, + uint32_t height, + uint32_t color, + BufferTarget target = BufferTarget::Draw); void draw_mono_bitmap(uint32_t x, uint32_t y, uint32_t width, @@ -33,6 +45,10 @@ void draw_mono_bitmap(uint32_t x, uint32_t stride, uint32_t fg_color, uint32_t bg_color, - bool transparent_bg); -uint32_t peek_pixel(uint32_t x, uint32_t y); + bool transparent_bg, + BufferTarget target = BufferTarget::Draw); +uint32_t peek_pixel(uint32_t x, + uint32_t y, + BufferTarget target = BufferTarget::Display); +void present(); } diff --git a/src/kernel/debug.cpp b/src/kernel/debug.cpp index 6cc1176..4df9bff 100644 --- a/src/kernel/debug.cpp +++ b/src/kernel/debug.cpp @@ -450,6 +450,8 @@ void render_gui_panic_screen(const char *message, helper_color, "System halted. Check the serial console for additional information.", LINE_SPACING); + + framebuffer::present(); } } // namespace diff --git a/src/kernel/framebuffer.cpp b/src/kernel/framebuffer.cpp index 4505662..11f7a23 100644 --- a/src/kernel/framebuffer.cpp +++ b/src/kernel/framebuffer.cpp @@ -14,6 +14,89 @@ bool g_available = false; uint32_t g_bytes_per_pixel = 0; uint32_t g_physical_address_low = 0; uint32_t g_framebuffer_size = 0; +size_t g_frame_stride_bytes = 0; +uint32_t g_virtual_height = 0; +bool g_double_buffer_enabled = false; +uint32_t g_buffer_count = 1; +uint32_t g_display_buffer_index = 0; +uint32_t g_draw_buffer_index = 0; +bool g_frame_in_progress = false; + +uintptr_t buffer_base(uint32_t index) +{ + const size_t stride = g_frame_stride_bytes; + return g_info.address + static_cast(index) * static_cast(stride); +} + +uint8_t *framebuffer_ptr(BufferTarget target) +{ + if (!g_double_buffer_enabled) + { + (void)target; + return reinterpret_cast(g_info.address); + } + + const uint32_t index = (target == BufferTarget::Display) ? g_display_buffer_index : g_draw_buffer_index; + return reinterpret_cast(buffer_base(index)); +} + +void copy_buffer(uint32_t source_index, uint32_t dest_index) +{ + if (source_index == dest_index || g_frame_stride_bytes == 0) + { + return; + } + + uint8_t *src = reinterpret_cast(buffer_base(source_index)); + uint8_t *dst = reinterpret_cast(buffer_base(dest_index)); + memcpy(dst, src, g_frame_stride_bytes); +} + +void ensure_frame_started(bool preserve_contents) +{ + if (!g_double_buffer_enabled) + { + return; + } + + if (g_frame_in_progress) + { + return; + } + + if (preserve_contents) + { + copy_buffer(g_display_buffer_index, g_draw_buffer_index); + } + + g_frame_in_progress = true; +} + +bool try_enable_double_buffering(); + +inline void store_color(uint8_t *dst, uint32_t color) +{ + switch (g_bytes_per_pixel) + { + case 4: + *reinterpret_cast(dst) = color; + break; + case 3: + dst[0] = static_cast(color & 0xFF); + dst[1] = static_cast((color >> 8) & 0xFF); + dst[2] = static_cast((color >> 16) & 0xFF); + break; + case 2: + *reinterpret_cast(dst) = static_cast(color & 0xFFFF); + break; + default: + for (uint32_t i = 0; i < g_bytes_per_pixel; ++i) + { + dst[i] = static_cast((color >> (i * 8U)) & 0xFFU); + } + break; + } +} struct [[gnu::packed]] VbeModeInfo { @@ -242,6 +325,17 @@ uint16_t bga_read(uint16_t index) return inw(VBE_DISPI_IOPORT_DATA); } +void set_display_buffer(uint32_t index) +{ + if (!g_double_buffer_enabled) + { + return; + } + + const uint32_t offset_y = g_info.height * index; + bga_write(VBE_DISPI_INDEX_Y_OFFSET, static_cast(offset_y)); +} + bool adopt_framebuffer(const FrameBufferInfo &detected) { if (detected.width == 0 || detected.height == 0 || detected.pitch == 0 || detected.bpp == 0 || detected.address == 0) @@ -265,6 +359,13 @@ bool adopt_framebuffer(const FrameBufferInfo &detected) g_bytes_per_pixel = bytes_per_pixel; g_physical_address_low = static_cast(detected.address & 0xFFFFFFFFULL); g_framebuffer_size = static_cast(size); + g_frame_stride_bytes = static_cast(detected.pitch) * detected.height; + g_virtual_height = detected.height; + g_double_buffer_enabled = false; + g_buffer_count = 1; + g_display_buffer_index = 0; + g_draw_buffer_index = 0; + g_frame_in_progress = false; g_available = true; return true; } @@ -317,6 +418,63 @@ bool initialize_bochs(uint32_t width, uint32_t height, uint32_t bpp) return true; } +bool try_enable_double_buffering() +{ + if (!g_available) + { + return false; + } + + if (!bochs_available()) + { + return false; + } + + if (g_frame_stride_bytes == 0) + { + return false; + } + + constexpr uint32_t desired_buffers = 2; + const uint32_t desired_virtual_height = g_info.height * desired_buffers; + if (desired_virtual_height == 0 || desired_virtual_height > 0xFFFFu) + { + return false; + } + + bga_write(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED); + bga_write(VBE_DISPI_INDEX_VIRT_WIDTH, static_cast(g_info.width)); + bga_write(VBE_DISPI_INDEX_VIRT_HEIGHT, static_cast(desired_virtual_height)); + bga_write(VBE_DISPI_INDEX_Y_OFFSET, 0); + bga_write(VBE_DISPI_INDEX_ENABLE, static_cast(VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED)); + + const uint16_t actual_height = bga_read(VBE_DISPI_INDEX_VIRT_HEIGHT); + if (actual_height < desired_virtual_height) + { + bga_write(VBE_DISPI_INDEX_VIRT_HEIGHT, static_cast(g_info.height)); + bga_write(VBE_DISPI_INDEX_Y_OFFSET, 0); + bga_write(VBE_DISPI_INDEX_ENABLE, static_cast(VBE_DISPI_ENABLED | VBE_DISPI_LFB_ENABLED)); + debug("[FB] Double buffering unavailable (virt_height=%u)", actual_height); + return false; + } + + g_double_buffer_enabled = true; + g_buffer_count = desired_buffers; + g_virtual_height = desired_virtual_height; + g_framebuffer_size = static_cast(g_frame_stride_bytes * g_buffer_count); + g_display_buffer_index = 0; + g_draw_buffer_index = 1 % g_buffer_count; + g_frame_in_progress = false; + + copy_buffer(g_display_buffer_index, g_draw_buffer_index); + set_display_buffer(g_display_buffer_index); + + debug("[FB] Double buffering enabled (%u buffers, stride=%zu)", + g_buffer_count, + g_frame_stride_bytes); + return true; +} + bool initialize_from_multiboot(const multiboot_info_t *info) { if (!(info->flags & MULTIBOOT_INFO_FRAMEBUFFER_INFO)) @@ -374,11 +532,6 @@ bool initialize_from_multiboot(const multiboot_info_t *info) return true; } -inline uint8_t *framebuffer_ptr() -{ - return reinterpret_cast(g_info.address); -} - uint32_t pack_rgb24(uint8_t r, uint8_t g, uint8_t b) { return (static_cast(r) << 16) | @@ -394,17 +547,26 @@ bool initialize(const multiboot_info_t *info) g_bytes_per_pixel = 0; g_physical_address_low = 0; g_framebuffer_size = 0; + g_frame_stride_bytes = 0; + g_virtual_height = 0; + g_double_buffer_enabled = false; + g_buffer_count = 1; + g_display_buffer_index = 0; + g_draw_buffer_index = 0; + g_frame_in_progress = false; debug("[FB] Multiboot flags: 0x%x", info ? info->flags : 0U); if (info != nullptr && initialize_from_multiboot(info)) { + try_enable_double_buffering(); return true; } if (initialize_bochs(1024, 768, 32)) { override_framebuffer_address_from_pci(); + try_enable_double_buffering(); return true; } @@ -431,6 +593,11 @@ uint32_t framebuffer_size() return g_framebuffer_size; } +bool double_buffering_enabled() +{ + return g_double_buffer_enabled; +} + void update_address(uintptr_t new_address) { g_info.address = new_address; @@ -461,7 +628,12 @@ uint32_t pack_color(uint8_t r, uint8_t g, uint8_t b) } } -void fill_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint32_t color) +void fill_rect(uint32_t x, + uint32_t y, + uint32_t width, + uint32_t height, + uint32_t color, + BufferTarget target) { if (!g_available) { @@ -487,40 +659,38 @@ void fill_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint32_t height = g_info.height - y; } - uint8_t *base = framebuffer_ptr(); + const bool draw_target = (target == BufferTarget::Draw); + if (draw_target) + { + const bool covers_full_frame = (x == 0) && (y == 0) && (width == g_info.width) && (height == g_info.height); + ensure_frame_started(!covers_full_frame); + } + + uint8_t *base = framebuffer_ptr(target); const uint32_t row_stride = g_info.pitch; + const uint32_t pixel_stride = g_bytes_per_pixel; - for (uint32_t row = 0; row < height; ++row) + if (g_bytes_per_pixel == 4) { - uint8_t *row_ptr = base + (y + row) * row_stride + x * g_bytes_per_pixel; - for (uint32_t col = 0; col < width; ++col) + for (uint32_t row = 0; row < height; ++row) { - uint8_t *pixel = row_ptr + col * g_bytes_per_pixel; - switch (g_bytes_per_pixel) + uint8_t *row_base = base + (y + row) * row_stride + x * pixel_stride; + uint32_t *dst = reinterpret_cast(row_base); + for (uint32_t col = 0; col < width; ++col) { - case 4: - { - uint32_t value = color; - memcpy(pixel, &value, sizeof(uint32_t)); - break; - } - case 3: - { - pixel[0] = static_cast(color & 0xFF); - pixel[1] = static_cast((color >> 8) & 0xFF); - pixel[2] = static_cast((color >> 16) & 0xFF); - break; - } - case 2: - { - uint16_t value = static_cast(color & 0xFFFF); - memcpy(pixel, &value, sizeof(uint16_t)); - break; - } - default: - break; + dst[col] = color; } } + return; + } + + for (uint32_t row = 0; row < height; ++row) + { + uint8_t *row_base = base + (y + row) * row_stride + x * pixel_stride; + for (uint32_t col = 0; col < width; ++col) + { + store_color(row_base + col * pixel_stride, color); + } } } @@ -532,16 +702,47 @@ void draw_mono_bitmap(uint32_t x, uint32_t stride, uint32_t fg_color, uint32_t bg_color, - bool transparent_bg) + bool transparent_bg, + BufferTarget target) { if (!g_available || bitmap == nullptr) { return; } + if (width == 0 || height == 0) + { + return; + } + + if (x >= g_info.width || y >= g_info.height) + { + return; + } + + if (x + width > g_info.width) + { + width = g_info.width - x; + } + if (y + height > g_info.height) + { + height = g_info.height - y; + } + + const bool draw_target = (target == BufferTarget::Draw); + if (draw_target) + { + ensure_frame_started(true); + } + + uint8_t *base = framebuffer_ptr(target); + const uint32_t row_stride = g_info.pitch; + const uint32_t pixel_stride = g_bytes_per_pixel; + for (uint32_t row = 0; row < height; ++row) { const uint8_t *bitmap_row = bitmap + row * stride; + uint8_t *row_base = base + (y + row) * row_stride + x * pixel_stride; for (uint32_t col = 0; col < width; ++col) { const uint32_t byte_index = col / 8; @@ -552,12 +753,12 @@ void draw_mono_bitmap(uint32_t x, continue; } uint32_t color = bit_set ? fg_color : bg_color; - fill_rect(x + col, y + row, 1, 1, color); + store_color(row_base + col * pixel_stride, color); } } } -uint32_t peek_pixel(uint32_t x, uint32_t y) +uint32_t peek_pixel(uint32_t x, uint32_t y, BufferTarget target) { if (!g_available) { @@ -569,7 +770,7 @@ uint32_t peek_pixel(uint32_t x, uint32_t y) return 0; } - const uint8_t *base = framebuffer_ptr(); + const uint8_t *base = framebuffer_ptr(target); const uint8_t *pixel = base + y * g_info.pitch + x * g_bytes_per_pixel; switch (g_bytes_per_pixel) @@ -597,4 +798,22 @@ uint32_t peek_pixel(uint32_t x, uint32_t y) } } +void present() +{ + if (!g_available || !g_double_buffer_enabled) + { + return; + } + + if (!g_frame_in_progress) + { + return; + } + + set_display_buffer(g_draw_buffer_index); + g_display_buffer_index = g_draw_buffer_index; + g_draw_buffer_index = (g_draw_buffer_index + 1) % g_buffer_count; + g_frame_in_progress = false; +} + } // namespace framebuffer diff --git a/src/kernel/gui.cpp b/src/kernel/gui.cpp index ee02575..9cff073 100644 --- a/src/kernel/gui.cpp +++ b/src/kernel/gui.cpp @@ -189,7 +189,12 @@ void restore_cursor_background() { break; } - framebuffer::fill_rect(px, py, 1, 1, g_cursor.background[row][col]); + framebuffer::fill_rect(px, + py, + 1, + 1, + g_cursor.background[row][col], + framebuffer::BufferTarget::Display); } } g_cursor.saved_height = 0; @@ -225,7 +230,8 @@ void render_mouse_cursor() CURSOR_STRIDE, outline_color, 0, - true); + true, + framebuffer::BufferTarget::Display); framebuffer::draw_mono_bitmap(static_cast(g_cursor.x), static_cast(g_cursor.y), CURSOR_WIDTH, @@ -234,7 +240,8 @@ void render_mouse_cursor() CURSOR_STRIDE, fill_color, outline_color, - true); + true, + framebuffer::BufferTarget::Display); g_cursor.drawn = true; } @@ -295,6 +302,7 @@ void draw_boot_screen() clear_background_fill_override(); suspend_mouse_cursor(); draw_background_gradient(); + framebuffer::present(); render_mouse_cursor(); } @@ -440,6 +448,7 @@ void begin_window_redraw() void end_window_redraw() { + framebuffer::present(); if (g_cursor.visible && !g_cursor.drawn) { render_mouse_cursor(); diff --git a/src/kernel/kernel.cpp b/src/kernel/kernel.cpp index df1e906..206a29d 100644 --- a/src/kernel/kernel.cpp +++ b/src/kernel/kernel.cpp @@ -47,6 +47,14 @@ extern "C" serial_init(); multiboot_info_t *mb_info = reinterpret_cast(multiboot_info_ptr); bool framebuffer_ready = framebuffer::initialize(mb_info); + if (framebuffer_ready) + { + debug("Framebuffer ready (%ux%u@%u) double buffering %s", + framebuffer::info().width, + framebuffer::info().height, + framebuffer::info().bpp, + framebuffer::double_buffering_enabled() ? "on" : "off"); + } const char *ascii_guitar = R"( Q diff --git a/src/kernel/terminal_windows.cpp b/src/kernel/terminal_windows.cpp index 87120e9..3141f47 100644 --- a/src/kernel/terminal_windows.cpp +++ b/src/kernel/terminal_windows.cpp @@ -29,6 +29,12 @@ struct Window Terminal::Snapshot snapshot{}; uint32_t frame_x = 0; uint32_t frame_y = 0; + uint32_t frame_width = 0; + uint32_t frame_height = 0; + uint32_t content_width = 0; + uint32_t content_height = 0; + uint32_t visible_columns = Terminal::VGA_WIDTH; + uint32_t visible_rows = Terminal::VGA_HEIGHT; DirtyRegion dirty{}; }; @@ -48,6 +54,20 @@ constexpr uint32_t INITIAL_FRAME_X = 40; constexpr uint32_t INITIAL_FRAME_Y = 72; constexpr uint32_t CASCADE_STEP_X = 28; constexpr uint32_t CASCADE_STEP_Y = 28; +constexpr uint32_t RESIZE_MARGIN = 12; +constexpr uint32_t MIN_VISIBLE_COLUMNS = 24; +constexpr uint32_t MIN_VISIBLE_ROWS = 12; + +constexpr uint32_t CLOSE_BUTTON_SIZE = 14; +constexpr uint32_t CLOSE_BUTTON_MARGIN = 8; + +constexpr RGB CLOSE_BUTTON_BG{200, 60, 70}; +constexpr RGB CLOSE_BUTTON_FG{240, 240, 240}; +constexpr RGB FRAME_BORDER_HIGHLIGHT{38, 44, 58}; +constexpr RGB TITLE_TOP_HIGHLIGHT{130, 150, 210}; +constexpr RGB TITLE_BOTTOM_SHADOW{18, 20, 30}; +constexpr RGB ACTIVE_ACCENT_GLOW{100, 160, 255}; +constexpr RGB TITLE_TEXT_SHADOW{18, 24, 32}; constexpr uint32_t CLOSE_BUTTON_SIZE = 14; constexpr uint32_t CLOSE_BUTTON_MARGIN = 8; @@ -89,6 +109,12 @@ constexpr RGB VGA_PALETTE[16] = { }; constexpr char FALLBACK_GLYPH = '?'; +constexpr uint8_t RESIZE_EDGE_NONE = 0; +constexpr uint8_t RESIZE_EDGE_LEFT = 1u << 0; +constexpr uint8_t RESIZE_EDGE_RIGHT = 1u << 1; +constexpr uint8_t RESIZE_EDGE_TOP = 1u << 2; +constexpr uint8_t RESIZE_EDGE_BOTTOM = 1u << 3; + Window g_windows[MAX_WINDOWS]; size_t g_window_count = 0; int g_active_slot = -1; @@ -96,10 +122,6 @@ int g_active_slot = -1; size_t g_z_order[MAX_WINDOWS]; size_t g_z_count = 0; -uint32_t g_content_width = 0; -uint32_t g_content_height = 0; -uint32_t g_frame_width = 0; -uint32_t g_frame_height = 0; uint32_t g_content_offset_x = 0; uint32_t g_content_offset_y = 0; bool g_geometry_ready = false; @@ -117,6 +139,7 @@ struct WindowHitResult bool on_close_button = false; uint32_t local_x = 0; uint32_t local_y = 0; + uint8_t resize_edges = RESIZE_EDGE_NONE; }; bool g_dragging_window = false; @@ -124,6 +147,16 @@ int g_drag_slot = -1; int32_t g_drag_offset_x = 0; int32_t g_drag_offset_y = 0; +bool g_resizing_window = false; +int g_resize_slot = -1; +uint8_t g_resize_edges = RESIZE_EDGE_NONE; +int32_t g_resize_start_cursor_x = 0; +int32_t g_resize_start_cursor_y = 0; +uint32_t g_resize_start_frame_x = 0; +uint32_t g_resize_start_frame_y = 0; +uint32_t g_resize_start_frame_width = 0; +uint32_t g_resize_start_frame_height = 0; + constexpr int INVALID_SLOT = -1; bool window_slot_valid(size_t slot) @@ -230,6 +263,12 @@ void reset_dirty(DirtyRegion ®ion) } void stop_dragging(); +void stop_resizing(); +uint32_t clamp_u32(uint32_t value, uint32_t min_value, uint32_t max_value); +uint32_t content_padding_width(); +uint32_t content_padding_height(); +void apply_visible_dimensions(Window &window, uint32_t columns, uint32_t rows); +void clamp_frame(uint32_t &frame_x, uint32_t &frame_y, uint32_t frame_width, uint32_t frame_height); void close_window_slot(size_t slot, Terminal &terminal) { @@ -243,6 +282,11 @@ void close_window_slot(size_t slot, Terminal &terminal) stop_dragging(); } + if (g_resizing_window && g_resize_slot == static_cast(slot)) + { + stop_resizing(); + } + remove_slot_from_stack(slot); release_window_slot(slot); @@ -307,6 +351,388 @@ void stop_dragging() g_drag_offset_y = 0; } +void stop_resizing() +{ + g_resizing_window = false; + g_resize_slot = INVALID_SLOT; + g_resize_edges = RESIZE_EDGE_NONE; + g_resize_start_cursor_x = 0; + g_resize_start_cursor_y = 0; + g_resize_start_frame_x = 0; + g_resize_start_frame_y = 0; + g_resize_start_frame_width = 0; + g_resize_start_frame_height = 0; +} + +void begin_resize(size_t slot, uint8_t edges, uint32_t cursor_x, uint32_t cursor_y) +{ + if (!window_slot_valid(slot)) + { + return; + } + + const Window &window = g_windows[slot]; + + g_resizing_window = true; + g_resize_slot = static_cast(slot); + g_resize_edges = edges; + g_resize_start_cursor_x = static_cast(cursor_x); + g_resize_start_cursor_y = static_cast(cursor_y); + g_resize_start_frame_x = window.frame_x; + g_resize_start_frame_y = window.frame_y; + g_resize_start_frame_width = window.frame_width; + g_resize_start_frame_height = window.frame_height; +} + +bool update_resize(Terminal &terminal, uint32_t cursor_x, uint32_t cursor_y) +{ + if (!g_resizing_window || g_resize_slot < 0 || !window_slot_valid(static_cast(g_resize_slot))) + { + return false; + } + + Window &window = g_windows[static_cast(g_resize_slot)]; + const auto &fb = framebuffer::info(); + if (fb.width == 0 || fb.height == 0) + { + return false; + } + + const uint32_t padding_w = content_padding_width(); + const uint32_t padding_h = content_padding_height(); + + const int32_t start_left = static_cast(g_resize_start_frame_x); + const int32_t start_top = static_cast(g_resize_start_frame_y); + const int32_t start_right = static_cast(g_resize_start_frame_x + g_resize_start_frame_width); + const int32_t start_bottom = static_cast(g_resize_start_frame_y + g_resize_start_frame_height); + + int32_t left = start_left; + int32_t top = start_top; + int32_t right = start_right; + int32_t bottom = start_bottom; + + const int32_t delta_x = static_cast(cursor_x) - g_resize_start_cursor_x; + const int32_t delta_y = static_cast(cursor_y) - g_resize_start_cursor_y; + + const int32_t min_frame_width = static_cast(MIN_VISIBLE_COLUMNS * gui::FONT_WIDTH + padding_w); + const int32_t min_frame_height = static_cast(MIN_VISIBLE_ROWS * gui::FONT_HEIGHT + padding_h); + + if (g_resize_edges & RESIZE_EDGE_LEFT) + { + left = start_left + delta_x; + if (left < 0) + { + left = 0; + } + if (start_right - left < min_frame_width) + { + left = start_right - min_frame_width; + } + } + + if (g_resize_edges & RESIZE_EDGE_RIGHT) + { + right = start_right + delta_x; + if (right > static_cast(fb.width)) + { + right = static_cast(fb.width); + } + if (right - left < min_frame_width) + { + right = left + min_frame_width; + } + } + + if (g_resize_edges & RESIZE_EDGE_TOP) + { + top = start_top + delta_y; + if (top < 0) + { + top = 0; + } + if (start_bottom - top < min_frame_height) + { + top = start_bottom - min_frame_height; + } + } + + if (g_resize_edges & RESIZE_EDGE_BOTTOM) + { + bottom = start_bottom + delta_y; + if (bottom > static_cast(fb.height)) + { + bottom = static_cast(fb.height); + } + if (bottom - top < min_frame_height) + { + bottom = top + min_frame_height; + } + } + + if (right <= left) + { + right = left + min_frame_width; + } + if (bottom <= top) + { + bottom = top + min_frame_height; + } + + if (right > static_cast(fb.width)) + { + right = static_cast(fb.width); + left = right - min_frame_width; + if (left < 0) + { + left = 0; + } + } + + if (bottom > static_cast(fb.height)) + { + bottom = static_cast(fb.height); + top = bottom - min_frame_height; + if (top < 0) + { + top = 0; + } + } + + int32_t width = right - left; + int32_t height = bottom - top; + + if (width < min_frame_width) + { + width = min_frame_width; + if (g_resize_edges & RESIZE_EDGE_LEFT) + { + left = right - width; + } + else + { + right = left + width; + } + } + + if (height < min_frame_height) + { + height = min_frame_height; + if (g_resize_edges & RESIZE_EDGE_TOP) + { + top = bottom - height; + } + else + { + bottom = top + height; + } + } + + const uint32_t max_columns_fb = (fb.width > padding_w) ? ((fb.width - padding_w) / gui::FONT_WIDTH) : MIN_VISIBLE_COLUMNS; + const uint32_t max_rows_fb = (fb.height > padding_h) ? ((fb.height - padding_h) / gui::FONT_HEIGHT) : MIN_VISIBLE_ROWS; + + uint32_t proposed_content_width = width > static_cast(padding_w) + ? static_cast(width - static_cast(padding_w)) + : 0U; + uint32_t proposed_columns = proposed_content_width / gui::FONT_WIDTH; + proposed_columns = clamp_u32(proposed_columns, + MIN_VISIBLE_COLUMNS, + max_columns_fb < MIN_VISIBLE_COLUMNS ? MIN_VISIBLE_COLUMNS + : (max_columns_fb > Terminal::VGA_WIDTH ? Terminal::VGA_WIDTH : max_columns_fb)); + + uint32_t proposed_content_height = height > static_cast(padding_h) + ? static_cast(height - static_cast(padding_h)) + : 0U; + uint32_t proposed_rows = proposed_content_height / gui::FONT_HEIGHT; + proposed_rows = clamp_u32(proposed_rows, + MIN_VISIBLE_ROWS, + max_rows_fb < MIN_VISIBLE_ROWS ? MIN_VISIBLE_ROWS + : (max_rows_fb > Terminal::VGA_HEIGHT ? Terminal::VGA_HEIGHT : max_rows_fb)); + + const uint32_t new_frame_width = proposed_columns * gui::FONT_WIDTH + padding_w; + const uint32_t new_frame_height = proposed_rows * gui::FONT_HEIGHT + padding_h; + + uint32_t new_frame_x = static_cast(left < 0 ? 0 : left); + uint32_t new_frame_y = static_cast(top < 0 ? 0 : top); + + if ((g_resize_edges & RESIZE_EDGE_LEFT) && !(g_resize_edges & RESIZE_EDGE_RIGHT)) + { + const uint32_t anchor_right = right < 0 ? 0U : static_cast(right); + if (anchor_right < new_frame_width) + { + new_frame_x = 0; + } + else + { + new_frame_x = anchor_right - new_frame_width; + } + } + + if ((g_resize_edges & RESIZE_EDGE_TOP) && !(g_resize_edges & RESIZE_EDGE_BOTTOM)) + { + const uint32_t anchor_bottom = bottom < 0 ? 0U : static_cast(bottom); + if (anchor_bottom < new_frame_height) + { + new_frame_y = 0; + } + else + { + new_frame_y = anchor_bottom - new_frame_height; + } + } + + clamp_frame(new_frame_x, new_frame_y, new_frame_width, new_frame_height); + + if (new_frame_width == window.frame_width && + new_frame_height == window.frame_height && + new_frame_x == window.frame_x && + new_frame_y == window.frame_y) + { + return false; + } + + const uint32_t old_frame_x = window.frame_x; + const uint32_t old_frame_y = window.frame_y; + const uint32_t old_frame_width = window.frame_width; + const uint32_t old_frame_height = window.frame_height; + + apply_visible_dimensions(window, proposed_columns, proposed_rows); + window.frame_x = new_frame_x; + window.frame_y = new_frame_y; + mark_full_dirty(window.dirty); + + if (old_frame_width > 0 && old_frame_height > 0) + { + gui::fill_background_rect(old_frame_x, old_frame_y, old_frame_width, old_frame_height); + } + + draw_windows(terminal); + return true; +} + +WindowHitResult hit_test_window(uint32_t x, uint32_t y) +{ + WindowHitResult result{}; + if (!framebuffer::is_available() || !g_geometry_ready || g_z_count == 0) + { + return result; + } + + auto test_slot = [&](size_t slot) -> bool { + if (!window_slot_valid(slot)) + { + return false; + } + + const Window &window = g_windows[slot]; + + const uint32_t frame_x = window.frame_x; + const uint32_t frame_y = window.frame_y; + + if (x < frame_x || y < frame_y) + { + return false; + } + + const uint32_t rel_x = x - frame_x; + const uint32_t rel_y = y - frame_y; + + const uint32_t frame_width = window.frame_width; + const uint32_t frame_height = window.frame_height; + + if (rel_x >= frame_width || rel_y >= frame_height) + { + return false; + } + + const uint32_t inner_width = frame_width > 2U * FRAME_BORDER ? frame_width - 2U * FRAME_BORDER : 0U; + const uint32_t close_x_start = FRAME_BORDER + (inner_width > CLOSE_BUTTON_SIZE + CLOSE_BUTTON_MARGIN + ? inner_width - CLOSE_BUTTON_MARGIN - CLOSE_BUTTON_SIZE + : inner_width); + const uint32_t close_y_start = FRAME_BORDER + (TITLE_BAR_HEIGHT > CLOSE_BUTTON_SIZE + ? (TITLE_BAR_HEIGHT - CLOSE_BUTTON_SIZE) / 2U + : 0U); + if (inner_width > CLOSE_BUTTON_SIZE + CLOSE_BUTTON_MARGIN && + rel_x >= close_x_start && rel_x < close_x_start + CLOSE_BUTTON_SIZE && + rel_y >= close_y_start && rel_y < close_y_start + CLOSE_BUTTON_SIZE) + { + result.slot = static_cast(slot); + result.on_close_button = true; + result.on_title_bar = false; + result.local_x = rel_x; + result.local_y = rel_y; + return true; + } + + const uint32_t dist_right = frame_width > rel_x ? frame_width - rel_x : 0U; + const uint32_t dist_bottom = frame_height > rel_y ? frame_height - rel_y : 0U; + uint8_t resize_edges = RESIZE_EDGE_NONE; + if (rel_x < RESIZE_MARGIN) + { + resize_edges |= RESIZE_EDGE_LEFT; + } + if (dist_right <= RESIZE_MARGIN) + { + resize_edges |= RESIZE_EDGE_RIGHT; + } + if (rel_y < RESIZE_MARGIN && rel_y <= FRAME_BORDER + 4U) + { + resize_edges |= RESIZE_EDGE_TOP; + } + if (dist_bottom <= RESIZE_MARGIN) + { + resize_edges |= RESIZE_EDGE_BOTTOM; + } + + if (resize_edges != RESIZE_EDGE_NONE) + { + result.slot = static_cast(slot); + result.on_close_button = false; + result.on_title_bar = false; + result.local_x = rel_x; + result.local_y = rel_y; + result.resize_edges = resize_edges; + return true; + } + + const uint32_t title_region = FRAME_BORDER + TITLE_BAR_HEIGHT; + + result.slot = static_cast(slot); + result.on_close_button = false; + result.on_title_bar = (rel_y < title_region); + result.local_x = rel_x; + result.local_y = rel_y; + return true; + }; + + if (g_active_slot >= 0 && test_slot(static_cast(g_active_slot))) + { + return result; + } + + for (size_t i = g_z_count; i > 0; --i) + { + size_t slot = g_z_order[i - 1]; + if (static_cast(slot) == g_active_slot) + { + continue; + } + if (test_slot(slot)) + { + return result; + } + } + + return result; +} + +void stop_dragging() +{ + g_dragging_window = false; + g_drag_slot = INVALID_SLOT; + g_drag_offset_x = 0; + g_drag_offset_y = 0; +} + WindowHitResult hit_test_window(uint32_t x, uint32_t y) { WindowHitResult result{}; @@ -400,20 +826,56 @@ uint32_t content_origin_x(const Window &window); uint32_t content_origin_y(const Window &window); WindowHitResult hit_test_window(uint32_t x, uint32_t y); void stop_dragging(); +void stop_resizing(); +void begin_resize(size_t slot, uint8_t edges, uint32_t cursor_x, uint32_t cursor_y); +bool update_resize(Terminal &terminal, uint32_t cursor_x, uint32_t cursor_y); + +uint32_t clamp_u32(uint32_t value, uint32_t min_value, uint32_t max_value) +{ + if (value < min_value) + { + return min_value; + } + if (value > max_value) + { + return max_value; + } + return value; +} + +uint32_t content_padding_width() +{ + return 2U * FRAME_BORDER + 2U * CONTENT_PADDING_X; +} + +uint32_t content_padding_height() +{ + return 2U * FRAME_BORDER + TITLE_BAR_HEIGHT + CONTENT_PADDING_BOTTOM; +} + +void apply_visible_dimensions(Window &window, uint32_t columns, uint32_t rows) +{ + columns = clamp_u32(columns, MIN_VISIBLE_COLUMNS, Terminal::VGA_WIDTH); + rows = clamp_u32(rows, MIN_VISIBLE_ROWS, Terminal::VGA_HEIGHT); + + window.visible_columns = columns; + window.visible_rows = rows; + window.content_width = columns * gui::FONT_WIDTH; + window.content_height = rows * gui::FONT_HEIGHT; + window.frame_width = window.content_width + content_padding_width(); + window.frame_height = window.content_height + content_padding_height(); +} void ensure_geometry(Terminal &terminal) { + (void)terminal; if (g_geometry_ready || !framebuffer::is_available()) { return; } - g_content_width = static_cast(terminal.pixel_width()); - g_content_height = static_cast(terminal.pixel_height()); g_content_offset_x = FRAME_BORDER + CONTENT_PADDING_X; g_content_offset_y = FRAME_BORDER + TITLE_BAR_HEIGHT; - g_frame_width = g_content_width + 2U * FRAME_BORDER + 2U * CONTENT_PADDING_X; - g_frame_height = g_content_height + 2U * FRAME_BORDER + TITLE_BAR_HEIGHT + CONTENT_PADDING_BOTTOM; g_geometry_ready = true; } @@ -573,14 +1035,27 @@ void draw_snapshot_contents(const Window &window, bool active, size_t start_row return; } - if (start_row >= Terminal::VGA_HEIGHT) + if (window.visible_rows == 0 || window.visible_columns == 0) + { + return; + } + + const size_t max_rows = window.visible_rows <= Terminal::VGA_HEIGHT ? window.visible_rows : Terminal::VGA_HEIGHT; + const size_t max_cols = window.visible_columns <= Terminal::VGA_WIDTH ? window.visible_columns : Terminal::VGA_WIDTH; + + if (max_rows == 0 || max_cols == 0) { return; } - if (end_row >= Terminal::VGA_HEIGHT) + if (start_row >= max_rows) { - end_row = Terminal::VGA_HEIGHT - 1; + return; + } + + if (end_row >= max_rows) + { + end_row = max_rows - 1; } if (start_row > end_row) @@ -600,7 +1075,7 @@ void draw_snapshot_contents(const Window &window, bool active, size_t start_row break; } - for (size_t col = 0; col < Terminal::VGA_WIDTH; ++col) + for (size_t col = 0; col < max_cols; ++col) { const uint32_t cell_x = base_x + static_cast(col) * gui::FONT_WIDTH; if (cell_x >= framebuffer::info().width) @@ -656,7 +1131,9 @@ void draw_snapshot_contents(const Window &window, bool active, size_t start_row } } - if (active && window.snapshot.cursor_active) + if (active && window.snapshot.cursor_active && + window.snapshot.cursor_row < window.visible_rows && + window.snapshot.cursor_column < window.visible_columns) { const uint32_t caret_x = base_x + static_cast(window.snapshot.cursor_column) * gui::FONT_WIDTH; const uint32_t caret_y = base_y + static_cast(window.snapshot.cursor_row) * gui::FONT_HEIGHT; @@ -696,7 +1173,7 @@ void present_window_slot(size_t slot) reset_dirty(window.dirty); } -void clamp_frame(uint32_t &frame_x, uint32_t &frame_y) +void clamp_frame(uint32_t &frame_x, uint32_t &frame_y, uint32_t frame_width, uint32_t frame_height) { if (!framebuffer::is_available() || !g_geometry_ready) { @@ -707,22 +1184,22 @@ void clamp_frame(uint32_t &frame_x, uint32_t &frame_y) const auto &fb = framebuffer::info(); - if (g_frame_width >= fb.width) + if (frame_width >= fb.width) { frame_x = 0; } - else if (frame_x + g_frame_width > fb.width) + else if (frame_x + frame_width > fb.width) { - frame_x = fb.width - g_frame_width; + frame_x = fb.width - frame_width; } - if (g_frame_height >= fb.height) + if (frame_height >= fb.height) { frame_y = 0; } - else if (frame_y + g_frame_height > fb.height) + else if (frame_y + frame_height > fb.height) { - frame_y = fb.height - g_frame_height; + frame_y = fb.height - frame_height; } } @@ -812,13 +1289,19 @@ void draw_window_frame(const Window &window, bool active) const uint32_t frame_x = window.frame_x; const uint32_t frame_y = window.frame_y; + const uint32_t frame_width = window.frame_width; + const uint32_t frame_height = window.frame_height; + if (frame_width == 0 || frame_height == 0) + { + return; + } const uint32_t inner_x = frame_x + FRAME_BORDER; const uint32_t inner_y = frame_y + FRAME_BORDER; - const uint32_t inner_width = g_frame_width > 2U * FRAME_BORDER ? g_frame_width - 2U * FRAME_BORDER : 0; - const uint32_t inner_height = g_frame_height > 2U * FRAME_BORDER ? g_frame_height - 2U * FRAME_BORDER : 0; + const uint32_t inner_width = frame_width > 2U * FRAME_BORDER ? frame_width - 2U * FRAME_BORDER : 0; + const uint32_t inner_height = frame_height > 2U * FRAME_BORDER ? frame_height - 2U * FRAME_BORDER : 0; - framebuffer::fill_rect(frame_x, frame_y, g_frame_width, g_frame_height, pack(FRAME_BORDER_COLOR)); - framebuffer::fill_rect(frame_x, frame_y, g_frame_width, 1, pack(FRAME_BORDER_HIGHLIGHT)); + framebuffer::fill_rect(frame_x, frame_y, frame_width, frame_height, pack(FRAME_BORDER_COLOR)); + framebuffer::fill_rect(frame_x, frame_y, frame_width, 1, pack(FRAME_BORDER_HIGHLIGHT)); if (inner_width == 0 || inner_height == 0) { @@ -928,7 +1411,14 @@ void render_window(Terminal &terminal, Window &window, bool active) { (void)terminal; - framebuffer::fill_rect(content_origin_x(window), content_origin_y(window), g_content_width, g_content_height, pack(CONTENT_BACKGROUND_COLOR)); + if (window.content_width > 0 && window.content_height > 0) + { + framebuffer::fill_rect(content_origin_x(window), + content_origin_y(window), + window.content_width, + window.content_height, + pack(CONTENT_BACKGROUND_COLOR)); + } draw_window_frame(window, active); draw_snapshot_contents(window, active); reset_dirty(window.dirty); @@ -972,6 +1462,7 @@ void reset_windows() g_z_count = 0; g_active_slot = INVALID_SLOT; stop_dragging(); + stop_resizing(); } } // namespace @@ -1061,11 +1552,12 @@ void request_new_window(Terminal &terminal, Process *proc) window.owner = proc; window.snapshot = g_blank_snapshot; reset_dirty(window.dirty); + apply_visible_dimensions(window, Terminal::VGA_WIDTH, Terminal::VGA_HEIGHT); const size_t cascade_index = g_z_count; uint32_t frame_x = INITIAL_FRAME_X + static_cast(cascade_index) * CASCADE_STEP_X; uint32_t frame_y = INITIAL_FRAME_Y + static_cast(cascade_index) * CASCADE_STEP_Y; - clamp_frame(frame_x, frame_y); + clamp_frame(frame_x, frame_y, window.frame_width, window.frame_height); window.frame_x = frame_x; window.frame_y = frame_y; @@ -1116,7 +1608,7 @@ void set_active_window_origin(Terminal &terminal, Process *proc, int32_t x, int3 uint32_t frame_x = desired_x > g_content_offset_x ? desired_x - g_content_offset_x : 0U; uint32_t frame_y = desired_y > g_content_offset_y ? desired_y - g_content_offset_y : 0U; - clamp_frame(frame_x, frame_y); + clamp_frame(frame_x, frame_y, window.frame_width, window.frame_height); window.frame_x = frame_x; window.frame_y = frame_y; @@ -1313,9 +1805,10 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) const uint32_t cursor_x = event.x < 0 ? 0U : static_cast(event.x); const uint32_t cursor_y = event.y < 0 ? 0U : static_cast(event.y); + const bool left_down = (event.buttons & MOUSE_BUTTON_LEFT) != 0; const bool left_changed = (event.changed & MOUSE_BUTTON_LEFT) != 0; - const bool left_pressed = left_changed && (event.buttons & MOUSE_BUTTON_LEFT); - const bool left_released = left_changed && (event.buttons & MOUSE_BUTTON_LEFT) == 0; + const bool left_pressed = left_changed && left_down; + const bool left_released = left_changed && !left_down; bool consumed = false; @@ -1344,6 +1837,7 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) if (hit.on_close_button) { stop_dragging(); + stop_resizing(); consumed = true; if (window.owner != nullptr) { @@ -1356,11 +1850,18 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) return true; } - if (g_active_slot >= 0 && window_slot_valid(static_cast(g_active_slot))) + if (hit.resize_edges != RESIZE_EDGE_NONE) + { + stop_dragging(); + begin_resize(static_cast(hit.slot), hit.resize_edges, cursor_x, cursor_y); + consumed = true; + } + else if (g_active_slot >= 0 && window_slot_valid(static_cast(g_active_slot))) { Window &active_window = g_windows[static_cast(g_active_slot)]; if (hit.on_title_bar) { + stop_resizing(); g_dragging_window = true; g_drag_slot = g_active_slot; g_drag_offset_x = static_cast(cursor_x) - static_cast(active_window.frame_x); @@ -1369,23 +1870,37 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) else { stop_dragging(); + stop_resizing(); } + consumed = true; } - - consumed = true; } else { stop_dragging(); + stop_resizing(); } } else if (left_released) { - if (g_dragging_window) + if (g_dragging_window || g_resizing_window) { consumed = true; } stop_dragging(); + stop_resizing(); + } + + if (g_resizing_window) + { + if (!left_down) + { + stop_resizing(); + } + else if (update_resize(terminal, cursor_x, cursor_y)) + { + return true; + } } if (g_dragging_window && g_drag_slot >= 0 && window_slot_valid(static_cast(g_drag_slot))) @@ -1393,6 +1908,8 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) Window &window = g_windows[static_cast(g_drag_slot)]; const uint32_t old_frame_x = window.frame_x; const uint32_t old_frame_y = window.frame_y; + const uint32_t old_frame_width = window.frame_width; + const uint32_t old_frame_height = window.frame_height; int32_t desired_x = static_cast(cursor_x) - g_drag_offset_x; int32_t desired_y = static_cast(cursor_y) - g_drag_offset_y; @@ -1407,13 +1924,13 @@ bool handle_mouse_event(Terminal &terminal, const MouseEvent &event) uint32_t frame_x = static_cast(desired_x); uint32_t frame_y = static_cast(desired_y); - clamp_frame(frame_x, frame_y); + clamp_frame(frame_x, frame_y, window.frame_width, window.frame_height); if (frame_x != old_frame_x || frame_y != old_frame_y) { - if (g_frame_width > 0 && g_frame_height > 0) + if (old_frame_width > 0 && old_frame_height > 0) { - gui::fill_background_rect(old_frame_x, old_frame_y, g_frame_width, g_frame_height); + gui::fill_background_rect(old_frame_x, old_frame_y, old_frame_width, old_frame_height); } window.frame_x = frame_x; window.frame_y = frame_y; diff --git a/src/kernel/vga.cpp b/src/kernel/vga.cpp index ce5cdf4..c750745 100644 --- a/src/kernel/vga.cpp +++ b/src/kernel/vga.cpp @@ -94,6 +94,11 @@ void Terminal::initialize() } } + if (framebuffer_mode) + { + framebuffer::present(); + } + update_cursor(); } @@ -154,7 +159,12 @@ void Terminal::draw_cursor() const uint32_t py = static_cast(origin_y_px + cursor_row * gui::FONT_HEIGHT); const uint32_t caret_color = framebuffer::pack_color(240, 240, 255); - framebuffer::fill_rect(px, py, 2, gui::FONT_HEIGHT, caret_color); + framebuffer::fill_rect(px, + py, + 2, + gui::FONT_HEIGHT, + caret_color, + framebuffer::BufferTarget::Display); cursor_active = true; } @@ -163,6 +173,7 @@ void Terminal::update_cursor() if (framebuffer_mode) { erase_cursor(); + framebuffer::present(); cursor_row = row; cursor_column = column; draw_cursor(); @@ -487,6 +498,11 @@ void Terminal::redraw_all() // Restore cursor overlay after full redraw. cursor_active = false; + + if (framebuffer_mode) + { + framebuffer::present(); + } } uint32_t Terminal::vga_color_to_rgb(uint8_t vga_color_value) const