Add drag-and-drop quadrant preview overlay
During a window title-bar drag, render a semi-transparent tint over the destination window to preview the drop outcome: - Full-window tint (quadrant=5) when hovering any window in swap-based layouts (Tall, Stack, Fat, etc.) or over a title bar — swap is the result so the whole window is highlighted. - Half-window directional tint (quadrant 1-4) when hovering in the Splits layout — a real directional insert happens so only the target half is highlighted. - 150ms fade-in; clears immediately on drop or drag exit. Implementation follows the visual_bell TINT_PROGRAM infrastructure: two new Screen fields (start_drag_overlay_at, drag_overlay_quadrant), draw_drag_preview_overlay() in shaders.c called from draw_cells() after both render paths, set_window_drag_overlay() C→Python bridge in state.c, and the same child-monitor.c needs_render + set_maximum_wait hooks that visual_bell uses to keep frames firing during animation. Layout detection uses hasattr(layout, 'insert_window_next_to') — the same guard _insert_window_in_direction uses internally — so the overlay always matches the actual drop behavior.
This commit is contained in:
parent
39820e79ff
commit
2c81a69aad
6 changed files with 86 additions and 20 deletions
|
|
@ -811,6 +811,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
|
|||
}
|
||||
if (send_cell_data_to_gpu(WD.vao_idx, WD.screen, os_window)) needs_render = true;
|
||||
if (WD.screen->start_visual_bell_at != 0) needs_render = true;
|
||||
if (WD.screen->start_drag_overlay_at != 0) needs_render = true;
|
||||
// Prepare window title bar screen data for GPU
|
||||
WindowRenderData *trd = &w->window_title_render_data;
|
||||
if (trd->screen && trd->geometry.bottom > trd->geometry.top && trd->geometry.right > trd->geometry.left) {
|
||||
|
|
@ -877,6 +878,7 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co
|
|||
if (is_active_window) active_window = w;
|
||||
draw_cells(&WD, os_window, is_active_window, false, num_of_visible_windows == 1, w);
|
||||
if (WD.screen->start_visual_bell_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT);
|
||||
if (WD.screen->start_drag_overlay_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT);
|
||||
WindowRenderData *trd = &w->window_title_render_data;
|
||||
if (trd->screen && trd->geometry.right > trd->geometry.left && trd->geometry.bottom > trd->geometry.top)
|
||||
draw_cells(trd, os_window, i == tab->active_window, true, false, NULL);
|
||||
|
|
|
|||
|
|
@ -1536,6 +1536,9 @@ def spawn(
|
|||
pass
|
||||
|
||||
|
||||
def set_window_drag_overlay(os_window_id: int, tab_id: int, window_id: int, quadrant: int) -> None: ...
|
||||
|
||||
|
||||
def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int, top: int, right: int, bottom: int) -> None:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ typedef struct {
|
|||
ScreenModes modes, saved_modes;
|
||||
ColorProfile *color_profile;
|
||||
monotonic_t start_visual_bell_at;
|
||||
monotonic_t start_drag_overlay_at; // 0 = inactive
|
||||
uint8_t drag_overlay_quadrant; // 1=left 2=right 3=top 4=bottom 0=none
|
||||
|
||||
uint8_t *write_buf;
|
||||
size_t write_buf_sz, write_buf_used;
|
||||
|
|
|
|||
|
|
@ -787,6 +787,36 @@ draw_visual_bell(const UIRenderData *ui) {
|
|||
#undef COLOR
|
||||
}
|
||||
|
||||
static void
|
||||
draw_drag_preview_overlay(const UIRenderData *ui) {
|
||||
Screen *screen = ui->screen;
|
||||
if (!screen->start_drag_overlay_at || !screen->drag_overlay_quadrant) return;
|
||||
const monotonic_t elapsed = monotonic() - screen->start_drag_overlay_at;
|
||||
const monotonic_t fade_ms = ms_to_monotonic_t(150ll);
|
||||
float intensity = elapsed >= fade_ms ? 1.0f : (float)elapsed / (float)fade_ms;
|
||||
GLfloat left = -1.f, top = 1.f, right = 1.f, bottom = -1.f;
|
||||
switch (screen->drag_overlay_quadrant) {
|
||||
case 1: right = 0.f; break; // left half
|
||||
case 2: left = 0.f; break; // right half
|
||||
case 3: bottom = 0.f; break; // top half
|
||||
case 4: top = 0.f; break; // bottom half
|
||||
case 5: break; // full window (swap)
|
||||
default: return;
|
||||
}
|
||||
bind_program(TINT_PROGRAM);
|
||||
float a = intensity * 0.25f;
|
||||
#define COLOR(name, fallback) colorprofile_to_color_with_fallback(screen->color_profile, \
|
||||
screen->color_profile->overridden.name, screen->color_profile->configured.name, \
|
||||
screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback)
|
||||
color_type hint = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg);
|
||||
#undef COLOR
|
||||
#define C(shift) (srgb_color((hint >> shift) & 0xFF) * a)
|
||||
glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), a);
|
||||
#undef C
|
||||
glUniform4f(tint_program_layout.uniforms.edges, left, top, right, bottom);
|
||||
draw_quad(true, 0);
|
||||
}
|
||||
|
||||
static bool
|
||||
has_scrollbar(Window *w, Screen *screen) {
|
||||
if (screen->linebuf != screen->main_linebuf || !screen->historybuf->count) return false;
|
||||
|
|
@ -1209,6 +1239,7 @@ draw_cells(const WindowRenderData *srd, OSWindow *os_window, bool is_active_wind
|
|||
ui.screen_left, ui.screen_top, ui.screen_width, ui.screen_height, ui.full_framebuffer_height);
|
||||
if (ui.os_window->needs_layers) draw_cells_with_layers(&ui, srd->vao_idx);
|
||||
else draw_cells_without_layers(&ui, srd->vao_idx);
|
||||
draw_drag_preview_overlay(&ui);
|
||||
restore_viewport();
|
||||
}
|
||||
// }}}
|
||||
|
|
|
|||
|
|
@ -864,6 +864,25 @@ PYWRAP1(set_tab_bar_render_data) {
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_window_drag_overlay) {
|
||||
id_type os_window_id, tab_id, window_id;
|
||||
int quadrant;
|
||||
PA("KKKi", &os_window_id, &tab_id, &window_id, &quadrant);
|
||||
WITH_WINDOW(os_window_id, tab_id, window_id)
|
||||
Screen *s = window->render_data.screen;
|
||||
if (s) {
|
||||
if (quadrant == 0) {
|
||||
s->start_drag_overlay_at = 0;
|
||||
s->drag_overlay_quadrant = 0;
|
||||
} else if (s->drag_overlay_quadrant != (uint8_t)quadrant) {
|
||||
s->start_drag_overlay_at = monotonic();
|
||||
s->drag_overlay_quadrant = (uint8_t)quadrant;
|
||||
}
|
||||
}
|
||||
END_WITH_WINDOW
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PYWRAP1(set_window_title_bar_render_data) {
|
||||
WindowGeometry g = {0};
|
||||
id_type os_window_id, tab_id, window_id;
|
||||
|
|
@ -1632,6 +1651,7 @@ static PyMethodDef module_methods[] = {
|
|||
MW(set_tab_bar_render_data, METH_VARARGS),
|
||||
MW(set_window_title_bar_render_data, METH_VARARGS),
|
||||
MW(set_window_render_data, METH_VARARGS),
|
||||
MW(set_window_drag_overlay, METH_VARARGS),
|
||||
MW(set_window_padding, METH_VARARGS),
|
||||
MW(viewport_for_window, METH_VARARGS),
|
||||
MW(cell_size_for_window, METH_VARARGS),
|
||||
|
|
|
|||
|
|
@ -1124,6 +1124,7 @@ class TabBeingDropped(NamedTuple):
|
|||
|
||||
class WindowBeingDropped(NamedTuple):
|
||||
window_id: int # the window whose title bar is currently highlighted as a drop target
|
||||
quadrant: int = 0 # 0=none, 1=left, 2=right, 3=top, 4=bottom, 5=full(swap)
|
||||
|
||||
|
||||
class TabManager: # {{{
|
||||
|
|
@ -1881,21 +1882,26 @@ def _find_window_at(self, x: int, y: int) -> 'Window | None':
|
|||
return win
|
||||
return None
|
||||
|
||||
def _set_drag_target_window(self, window_id: int) -> None:
|
||||
"""Highlight window_id's title bar as the drop target; 0 clears."""
|
||||
def _set_drag_target_window(self, window_id: int, quadrant: int = 0) -> None:
|
||||
"""Highlight window_id's title bar as the drop target; 0 clears. quadrant!=0 shows quadrant overlay instead."""
|
||||
from .fast_data_types import set_window_drag_overlay
|
||||
boss = get_boss()
|
||||
prev_id = self.window_being_dropped.window_id if self.window_being_dropped else 0
|
||||
if prev_id == window_id:
|
||||
prev_quadrant = self.window_being_dropped.quadrant if self.window_being_dropped else 0
|
||||
if prev_id == window_id and prev_quadrant == quadrant:
|
||||
return
|
||||
if prev_id and (prev_w := boss.window_id_map.get(prev_id)):
|
||||
prev_w.is_drag_target = False
|
||||
set_window_drag_overlay(self.os_window_id, prev_w.tab_id, prev_id, 0)
|
||||
if prev_w._title_bar_screen is not None:
|
||||
tab = prev_w.tabref()
|
||||
prev_w.update_title_bar(is_active=tab is not None and tab.active_window is prev_w)
|
||||
if window_id and (new_w := boss.window_id_map.get(window_id)):
|
||||
new_w.is_drag_target = True
|
||||
new_w.update_title_bar(is_active=True)
|
||||
self.window_being_dropped = WindowBeingDropped(window_id=window_id)
|
||||
if quadrant == 5:
|
||||
new_w.is_drag_target = True
|
||||
new_w.update_title_bar(is_active=True)
|
||||
set_window_drag_overlay(self.os_window_id, new_w.tab_id, window_id, quadrant)
|
||||
self.window_being_dropped = WindowBeingDropped(window_id=window_id, quadrant=quadrant)
|
||||
else:
|
||||
self.window_being_dropped = None
|
||||
|
||||
|
|
@ -1912,21 +1918,23 @@ def on_window_drop_move(self, window_id: int = 0, is_dest: bool = False, x: int
|
|||
return
|
||||
self._set_drag_target_tab(0)
|
||||
dest_window = self._find_window_at(x, y)
|
||||
if dest_window and dest_window.id != window_id and dest_window.show_title_bar:
|
||||
from .fast_data_types import cell_size_for_window
|
||||
_, ch = cell_size_for_window(self.os_window_id)
|
||||
g = dest_window.geometry
|
||||
opts = get_options()
|
||||
tb_top = g.top if opts.window_title_bar == 'top' else g.bottom - ch
|
||||
tb_bottom = tb_top + ch
|
||||
from .fast_data_types import viewport_for_window
|
||||
central = viewport_for_window(self.os_window_id)[0]
|
||||
rel_y = y - central.top
|
||||
in_title_bar = tb_top <= rel_y < tb_bottom
|
||||
target_id = dest_window.id if in_title_bar else 0
|
||||
if dest_window and dest_window.id != window_id:
|
||||
quadrant = 5
|
||||
active_tab = self.active_tab
|
||||
if active_tab is not None and hasattr(active_tab.current_layout, 'insert_window_next_to'):
|
||||
from .fast_data_types import viewport_for_window as _vfw
|
||||
central = _vfw(self.os_window_id)[0]
|
||||
rel_x = x - central.left
|
||||
rel_y = y - central.top
|
||||
g = dest_window.geometry
|
||||
dx = rel_x - (g.left + g.right) / 2
|
||||
dy = rel_y - (g.top + g.bottom) / 2
|
||||
quad_map = {'left': 1, 'right': 2, 'top': 3, 'bottom': 4}
|
||||
direction = ('right' if dx > 0 else 'left') if abs(dx) >= abs(dy) else ('bottom' if dy > 0 else 'top')
|
||||
quadrant = quad_map[direction]
|
||||
self._set_drag_target_window(dest_window.id, quadrant)
|
||||
else:
|
||||
target_id = 0
|
||||
self._set_drag_target_window(target_id)
|
||||
self._set_drag_target_window(0)
|
||||
|
||||
def on_window_drop(self, x: int, y: int, window_id: int) -> None:
|
||||
from .fast_data_types import cell_size_for_window, viewport_for_window
|
||||
|
|
|
|||
Loading…
Reference in a new issue