Files
shaders_lab/NEXT_STEPS_BORDERLESS_WINDOW.md
2026-04-28 22:12:27 +02:00

7.9 KiB

shaders_lab — proximos pasos: ventana sin decoraciones del SO + botones min/max/close en la MainMenuBar

Motivacion

Hoy la ventana lleva la titlebar nativa de Windows/Linux ademas de la MainMenuBar de ImGui (View). Son dos barras consumiendo ~60 px verticales. Queremos:

  1. Recuperar ese espacio quitando la titlebar del SO.
  2. Integrar los botones min / max / close en la MainMenuBar de ImGui (la que renderiza panel_menu_cpp_core), alineados a la derecha.
  3. Resultado: una sola barra superior compacta con menus + botones de ventana, igual que VSCode/Spotify/etc.

Aplicable a todas las apps del registry, no solo shaders_lab — debe materializarse como funcion(es) reusables en cpp/functions/core/.


Que hace falta

1. Crear la ventana sin decoraciones

Una linea en cpp/framework/app_base.cpp (donde se crea la GLFW window, linea ~37):

glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);

Detras de un nuevo flag AppConfig::borderless = false para que las apps existentes sigan iguales por defecto.

2. Dibujar la titlebar custom

Hoy panel_menu_cpp_core ya pinta una BeginMainMenuBar() con el menu "View". Ampliamos esa misma barra con:

  • Titulo de la app a la izquierda (antes del primer menu) — opcional.
  • Espacio para drag en el centro (la propia barra ya es draggeable porque ImGui detecta clicks fuera de los items).
  • Tres botones a la derecha alineados con SameLine + offset: min (—), max (□ / ⧉ segun estado), close (✕).

Glifos: ImGui::ImDrawList con lineas/rect, sin necesidad de fuente de iconos. O symbols Unicode si la fuente los soporta.

3. Lo que el SO te daba gratis y hay que reimplementar

Drag de la ventana

// Detectar mouse-down en la titlebar (no sobre items):
if (!ImGui::IsAnyItemHovered() && ImGui::IsMouseDragging(0)) {
    double cx, cy; glfwGetCursorPos(window, &cx, &cy);
    int wx, wy;    glfwGetWindowPos(window, &wx, &wy);
    // guardar offset al iniciar drag, aplicar glfwSetWindowPos cada frame
}

Doble-click en titlebar → toggle maximize

if (ImGui::IsMouseDoubleClicked(0) && !ImGui::IsAnyItemHovered()) {
    if (glfwGetWindowAttrib(window, GLFW_MAXIMIZED))
        glfwRestoreWindow(window);
    else
        glfwMaximizeWindow(window);
}

Botones

glfwIconifyWindow(window);                    // min
glfwMaximizeWindow(window) / glfwRestoreWindow(window);  // max toggle
glfwSetWindowShouldClose(window, true);       // close

Resize desde bordes — la parte fea. Sin decoraciones GLFW no expone hit-test de bordes. Hay que detectar mouse en franjas de ~6 px en los 8 lados, cambiar cursor (glfwSetCursor con GLFW_HRESIZE_CURSOR etc.), y arrastrar reposicionando+resizing manualmente.


Lo que se pierde

Comportamiento Recuperable
Snap zones de Windows (Win+flecha, drag al borde) Solo con codigo nativo Win32 (WM_NCHITTEST) — GLFW no lo expone
Aero shake, sombras nativas, animacion de minimizar No facilmente
Snap del WM en Linux (i3/sway/KDE) Igual
Bugs Wayland posicionando ventanas borderless Si Linux es WSLg/X11 sin problema; en Wayland nativo verificar primero
Multi-viewport ImGui (ConfigFlags_ViewportsEnable) Cada ventana secundaria tambien sin titlebar → custom en todas. Mas curro
Touch / accesibilidad (lectores de pantalla) Marginal para nuestro caso

Plan de implementacion

Fase 1 — funcion reusable + flag en app_base

Funcion nueva: custom_titlebar_cpp_core (componente, pure desde el punto de vista del registry — solo dibuja UI; los efectos GLFW se aplican fuera o se pasan como callbacks). Idealmente fusionada con panel_menu_cpp_core o coordinada con ella para que el menu y los botones vivan en la misma MainMenuBar.

Opcion A (mejor): extender panel_menu con parametros opcionales para los botones del SO:

struct WindowControls {
    GLFWwindow* window;
    bool show_min   = true;
    bool show_max   = true;
    bool show_close = true;
};

bool panel_menu(const char* menu_label,
                const PanelToggle* items, std::size_t count,
                const WindowControls* controls = nullptr); // opcional

Pero esto crea acople de core con GLFW. Mejor opcion B:

Opcion B (limpia): funcion separada custom_titlebar_cpp_core que se llama dentro del menu existente (despues de los menus, antes del EndMainMenuBar) usando ImGui::SameLine con offset al borde derecho. Y una funcion auxiliar window_controls_cpp_core para los tres botones, que recibe callbacks (on_min, on_max, on_close) sin saber nada de GLFW. La app las cablea.

namespace fn_ui {

struct WindowButtons {
    bool min_clicked   = false;
    bool max_clicked   = false;
    bool close_clicked = false;
    bool is_maximized  = false; // input: pinta el icono correcto
};

// Renderiza tres iconos al borde derecho de la barra activa
// (BeginMainMenuBar o cualquier otro contenedor horizontal).
// Devuelve los flags que clico el usuario.
WindowButtons window_controls(bool is_maximized);

// Drag handler: llamar cada frame cuando mouse esta sobre la barra.
// Devuelve delta a aplicar a la posicion de ventana.
// (signature por afinar)
struct WindowDrag { int dx, dy; bool dragging; };
WindowDrag titlebar_drag_handler();

} // namespace fn_ui

La app conecta:

auto wb = fn_ui::window_controls(glfwGetWindowAttrib(window, GLFW_MAXIMIZED));
if (wb.min_clicked)   glfwIconifyWindow(window);
if (wb.max_clicked)   { /* toggle */ }
if (wb.close_clicked) glfwSetWindowShouldClose(window, true);

Asi core no toca GLFW; framework/app_base o cada app cablean lo nativo.

Cambio en app_base:

struct AppConfig {
    // ...existing...
    bool borderless = false; // true → GLFW_DECORATED=false
};

// en run_app():
if (config.borderless) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);

Fase 2 — integracion en shaders_lab

fn::AppConfig cfg;
cfg.borderless = true;
// ...

En render() despues del panel_menu("View", ...), en la misma barra (reorganizar para que panel_menu no cierre EndMainMenuBar y deje hacer SameLine al borde derecho con window_controls).

Fase 3 (opcional) — resize por bordes

Manejador de hit-test en 8 px alrededor del borde de la ventana. Cambia cursor con glfwSetCursor, en mouse-down inicia resize manual con glfwSetWindowSize + glfwSetWindowPos.

Fase 4 (solo si se nota la perdida) — snap de Windows nativo

WM_NCHITTEST via HWND. #ifdef _WIN32, glfwGetWin32Window, SetWindowLongPtr para subclassear. Trabajo significativo; postponer hasta haber medido si la falta de snap molesta de verdad.


Decisiones pendientes para el dia que se haga

  1. Resize manual por bordes en v1 o solo arrastre + maximizar.
  2. Si hacemos WM_NCHITTEST o aceptamos sin snap de Windows.
  3. Multi-viewport ImGui: queda off mientras la titlebar sea custom, o se replica el control en cada secondary window.
  4. Forma final del API: panel_menu extendido vs window_controls aparte (preferencia actual: aparte, mas limpia).

Mi recomendacion practica

Empezar minimal:

  • Borderless ON
  • Drag arrastrando la MainMenuBar
  • Doble-click maximiza/restaura
  • Botones min/max/close al borde derecho
  • Sin resize manual (la ventana es solo maximizable; util para apps tipo lab/dashboard)
  • Sin WM_NCHITTEST (sin snap de Windows)
  • Multi-viewport off

Eso es 80% del valor con 20% del trabajo. Si despues echamos en falta el resize manual o el snap, se anaden incremental.