Refactor state tracking for tab drag
Drop into same window now works
This commit is contained in:
parent
0b6636eb0f
commit
c56d4223af
7 changed files with 176 additions and 118 deletions
|
|
@ -111,6 +111,7 @@
|
|||
set_os_window_chrome,
|
||||
set_os_window_size,
|
||||
set_os_window_title,
|
||||
set_tab_being_dragged,
|
||||
start_drag_with_data,
|
||||
thread_write,
|
||||
toggle_fullscreen,
|
||||
|
|
@ -1894,10 +1895,11 @@ def update_tab_bar_data(self, os_window_id: int) -> None:
|
|||
def on_drop_move(self, os_window_id: int, x: int, y: int, from_self: bool) -> None:
|
||||
if (tm := self.os_window_map.get(os_window_id)) is None:
|
||||
return
|
||||
if from_self and (tab_id := get_tab_being_dragged()) and (tab := self.tab_for_id(tab_id)):
|
||||
if (source_tm := tab.tab_manager_ref()) and (state := source_tm.tab_drag_state) and state.tab_being_dragged:
|
||||
for tm in self.all_tab_managers:
|
||||
tm.on_tab_drop_move(state.tab_being_dragged, x, y)
|
||||
if from_self:
|
||||
tab_id, drag_started = get_tab_being_dragged()[:2]
|
||||
if drag_started:
|
||||
for q in self.all_tab_managers:
|
||||
q.on_tab_drop_move(tab_id, q is tm, x, y)
|
||||
|
||||
def on_drop(self, os_window_id: int, drop: dict[str, bytes] | int, from_self: bool, x: int, y: int) -> None:
|
||||
if isinstance(drop, int):
|
||||
|
|
@ -1910,6 +1912,12 @@ def on_drop(self, os_window_id: int, drop: dict[str, bytes] | int, from_self: bo
|
|||
return
|
||||
if (tm := self.os_window_map.get(os_window_id)) is None:
|
||||
return
|
||||
if f'application/net.kovidgoyal.kitty-tab-{os.getpid()}' in drop:
|
||||
tm.on_tab_drop(x, y)
|
||||
set_tab_being_dragged()
|
||||
for tm in self.all_tab_managers:
|
||||
tm.on_tab_drop_move(0, False, 0, 0)
|
||||
return
|
||||
central, tab_bar = viewport_for_window(os_window_id)[:2]
|
||||
if central.left <= x < central.right and central.top <= y < central.bottom:
|
||||
x -= central.left
|
||||
|
|
|
|||
|
|
@ -1019,9 +1019,8 @@ def cocoa_window_id(os_window_id: int) -> int:
|
|||
pass
|
||||
|
||||
|
||||
def swap_tabs(os_window_id: int, a: int, b: int) -> None:
|
||||
pass
|
||||
|
||||
def swap_tabs(os_window_id: int, a: int, b: int) -> None: ...
|
||||
def reorder_tabs(os_window_id: int, *tab_ids: int) -> None: ...
|
||||
|
||||
def set_active_tab(os_window_id: int, a: int) -> None:
|
||||
pass
|
||||
|
|
@ -1819,8 +1818,8 @@ def start_drag_with_data(
|
|||
operations: int = GLFW_DRAG_OPERATION_MOVE
|
||||
) -> None: ...
|
||||
|
||||
def set_tab_being_dragged(tab_id: int) -> None: ...
|
||||
def get_tab_being_dragged() -> int: ...
|
||||
def set_tab_being_dragged(tab_id: int = 0, drag_started: bool = False, x: float = 0, y: float = 0) -> None: ...
|
||||
def get_tab_being_dragged() -> tuple[int, bool, float, float]: ...
|
||||
def request_callback_with_thumbnail(
|
||||
callback: str, os_window_id: int, window_id: int = 0, include_tab_bar: bool = False,
|
||||
scale: float = 0.25, max_width: int = 480
|
||||
|
|
|
|||
|
|
@ -868,7 +868,7 @@ handle_tab_bar_mouse(int button, int modifiers, int action) {
|
|||
set_currently_hovered_window(0, modifiers);
|
||||
OSWindow *w = global_state.callback_os_window;
|
||||
// dont report motion events, as they are expensive and useless
|
||||
if (w && (button > -1 || global_state.tab_being_dragged)) {
|
||||
if (w && (button > -1 || global_state.tab_being_dragged.id)) {
|
||||
call_boss(handle_tab_bar_mouse, "Kddiii", w->id, w->mouse_x, w->mouse_y, button, modifiers, action);
|
||||
}
|
||||
}
|
||||
|
|
@ -1132,7 +1132,7 @@ mouse_event(const int button, int modifiers, int action) {
|
|||
w = window_for_event(&window_idx, &in_tab_bar);
|
||||
set_currently_hovered_window(w ? w->id : 0, modifiers);
|
||||
|
||||
if (in_tab_bar || global_state.tab_being_dragged) {
|
||||
if (in_tab_bar || global_state.tab_being_dragged.id) {
|
||||
mouse_cursor_shape = POINTER_POINTER;
|
||||
handle_tab_bar_mouse(button, modifiers, action);
|
||||
debug("handled by tab bar\n");
|
||||
|
|
|
|||
|
|
@ -569,6 +569,37 @@ swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
|
|||
END_WITH_OS_WINDOW
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
pyreorder_tabs(PyObject *self UNUSED, PyObject *args) {
|
||||
if (PyTuple_GET_SIZE(args) < 2) Py_RETURN_NONE;
|
||||
id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(args, 0));
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
if (PyTuple_GET_SIZE(args) != os_window->num_tabs + 1) { PyErr_SetString(PyExc_ValueError, "number of tabs not correct"); return NULL; }
|
||||
if (!os_window->num_tabs) Py_RETURN_NONE;
|
||||
RAII_ALLOC(Tab, tabs, calloc(os_window->capacity, sizeof(Tab)));
|
||||
RAII_ALLOC(char, used, calloc(os_window->num_tabs, sizeof(char)));
|
||||
if (!tabs || !used) { return PyErr_NoMemory(); }
|
||||
for (Py_ssize_t i = 1; i < PyTuple_GET_SIZE(args); i++) {
|
||||
id_type tab_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(args, i));
|
||||
bool found = false;
|
||||
for (size_t t = 0; t < os_window->num_tabs; t++) {
|
||||
if (os_window->tabs[t].id == tab_id) {
|
||||
tabs[i-1] = os_window->tabs[t];
|
||||
found = true;
|
||||
if (used[t]) {
|
||||
PyErr_SetString(PyExc_ValueError, "duplicate tab ids found"); return NULL;
|
||||
}
|
||||
used[t] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) { PyErr_SetString(PyExc_ValueError, "tab id not found"); return NULL; }
|
||||
}
|
||||
free(os_window->tabs); os_window->tabs = tabs; tabs = NULL;
|
||||
END_WITH_OS_WINDOW
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
pyset_borders_rects(PyObject *self UNUSED, PyObject *args) {
|
||||
id_type os_window_id, tab_id;
|
||||
|
|
@ -1483,17 +1514,19 @@ get_mouse_data_for_window(PyObject *self UNUSED, PyObject *args) {
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#define tbd global_state.tab_being_dragged
|
||||
static PyObject*
|
||||
set_tab_being_dragged(PyObject *self UNUSED, PyObject *args) {
|
||||
if (!PyLong_Check(args)) { PyErr_SetString(PyExc_TypeError, "tab id must be integer"); return NULL; }
|
||||
global_state.tab_being_dragged = PyLong_AsUnsignedLongLong(args);
|
||||
zero_at_ptr(&tbd);
|
||||
if (!PyArg_ParseTuple(args, "|Kpdd", &tbd.id, &tbd.drag_started, &tbd.x, &tbd.y)) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_tab_being_dragged(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
return PyLong_FromUnsignedLongLong(global_state.tab_being_dragged);
|
||||
return Py_BuildValue("KOdd", tbd.id, tbd.drag_started ? Py_True : Py_False, tbd.x, tbd.y);
|
||||
}
|
||||
#undef tbd
|
||||
|
||||
static PyObject*
|
||||
request_callback_with_thumbnail(PyObject *self UNUSED, PyObject *args) {
|
||||
|
|
@ -1521,7 +1554,7 @@ static PyMethodDef module_methods[] = {
|
|||
M(os_window_focus_counters, METH_NOARGS),
|
||||
M(get_mouse_data_for_window, METH_VARARGS),
|
||||
M(request_callback_with_thumbnail, METH_VARARGS),
|
||||
M(set_tab_being_dragged, METH_O),
|
||||
M(set_tab_being_dragged, METH_VARARGS),
|
||||
M(get_tab_being_dragged, METH_NOARGS),
|
||||
MW(update_pointer_shape, METH_VARARGS),
|
||||
MW(current_os_window, METH_NOARGS),
|
||||
|
|
@ -1554,6 +1587,7 @@ static PyMethodDef module_methods[] = {
|
|||
MW(buffer_keys_in_window, METH_VARARGS),
|
||||
MW(set_active_window, METH_VARARGS),
|
||||
MW(swap_tabs, METH_VARARGS),
|
||||
MW(reorder_tabs, METH_VARARGS),
|
||||
MW(set_borders_rects, METH_VARARGS),
|
||||
MW(set_tab_bar_render_data, METH_VARARGS),
|
||||
MW(set_window_render_data, METH_VARARGS),
|
||||
|
|
|
|||
|
|
@ -388,13 +388,16 @@ typedef struct GlobalState {
|
|||
int action;
|
||||
PyObject *drag_data;
|
||||
} drag_source;
|
||||
id_type tab_being_dragged;
|
||||
struct {
|
||||
id_type os_window, window;
|
||||
char callback[32];
|
||||
bool include_tab_bar;
|
||||
double scale; unsigned max_width;
|
||||
} thumbnail_callback;
|
||||
struct {
|
||||
id_type id; bool drag_started;
|
||||
double x, y;
|
||||
} tab_being_dragged;
|
||||
} GlobalState;
|
||||
|
||||
extern GlobalState global_state;
|
||||
|
|
|
|||
|
|
@ -55,10 +55,6 @@ class TabBarData(NamedTuple):
|
|||
session_name: str
|
||||
active_session_name: str
|
||||
|
||||
is_being_moved: bool = False
|
||||
drop_idx: int = 0
|
||||
pending_drop_idx: int = -1
|
||||
|
||||
|
||||
class DrawData(NamedTuple):
|
||||
leading_spaces: int
|
||||
|
|
@ -291,6 +287,7 @@ def apply_title_template(draw_data: DrawData, tab: TabBarData, index: int, max_t
|
|||
'num_window_groups': tab.num_window_groups,
|
||||
'title': tab.title,
|
||||
'tab': ta,
|
||||
'tab_id': tab.tab_id,
|
||||
'fmt': Formatter,
|
||||
'sup': SupSub(data),
|
||||
'sub': SupSub(data, True),
|
||||
|
|
|
|||
213
kitty/tabs.py
213
kitty/tabs.py
|
|
@ -31,6 +31,7 @@
|
|||
get_boss,
|
||||
get_click_interval,
|
||||
get_options,
|
||||
get_tab_being_dragged,
|
||||
is_tab_bar_visible,
|
||||
last_focused_os_window_id,
|
||||
mark_tab_bar_dirty,
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
next_window_id,
|
||||
remove_tab,
|
||||
remove_window,
|
||||
reorder_tabs,
|
||||
replace_c0_codes_except_nl_space_tab,
|
||||
request_callback_with_thumbnail,
|
||||
ring_bell,
|
||||
|
|
@ -85,15 +87,6 @@ class TabMouseEvent(NamedTuple):
|
|||
tab_id: int = 0
|
||||
|
||||
|
||||
class TabDragState(NamedTuple):
|
||||
tab_id: int
|
||||
start_x: float
|
||||
start_y: float
|
||||
original_index: int
|
||||
drag_started: bool = False # True if drag threshold exceeded
|
||||
tab_being_dragged: TabBarData | None = None # This is not None once the tab is handed off to the OS
|
||||
|
||||
|
||||
class TabDict(TypedDict):
|
||||
id: int
|
||||
is_focused: bool
|
||||
|
|
@ -1096,15 +1089,22 @@ def make_active(self) -> None:
|
|||
# }}}
|
||||
|
||||
|
||||
ignore_left_button_release: bool = False
|
||||
|
||||
|
||||
class TabBeingDropped(NamedTuple):
|
||||
data: TabBarData
|
||||
tab_ids: Sequence[int] = ()
|
||||
last_drop_move_x: int = -1
|
||||
|
||||
|
||||
class TabManager: # {{{
|
||||
|
||||
confirm_close_window_id: int = 0
|
||||
num_of_windows_with_progress: int = 0
|
||||
total_progress: int = 0
|
||||
has_indeterminate_progress: bool = False
|
||||
tab_drag_state: TabDragState | None = None
|
||||
tab_being_dropped: TabBarData | None = None
|
||||
last_drop_move_x: int = -1
|
||||
tab_being_dropped: TabBeingDropped | None = None
|
||||
|
||||
def __init__(self, os_window_id: int, args: CLIOptions, wm_class: str, wm_name: str, startup_session: SessionType | None = None):
|
||||
self.os_window_id = os_window_id
|
||||
|
|
@ -1201,7 +1201,8 @@ def tab_bar_should_be_visible(self) -> bool:
|
|||
count = get_options().tab_bar_min_tabs
|
||||
if count < 1:
|
||||
return True
|
||||
if self.tab_drag_state is not None and self.tab_drag_state.drag_started:
|
||||
tab_id, drag_started = get_tab_being_dragged()[:2]
|
||||
if drag_started and self.tab_for_id(tab_id) is not None:
|
||||
count += 1
|
||||
for t in self.tabs_to_be_shown_in_tab_bar:
|
||||
count -= 1
|
||||
|
|
@ -1420,8 +1421,7 @@ def move_tab(self, delta: int = 1) -> None:
|
|||
nidx = self.tabs.index(new_active_tab)
|
||||
step = 1 if idx < nidx else -1
|
||||
for i in range(idx, nidx, step):
|
||||
self.tabs[i], self.tabs[i + step] = self.tabs[i + step], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i + step)
|
||||
self.swap_tabs(i, i + step)
|
||||
self._set_active_tab(nidx)
|
||||
self.mark_tab_bar_dirty()
|
||||
|
||||
|
|
@ -1463,8 +1463,7 @@ def new_tab(
|
|||
desired_idx = self.tabs.index(tabs[desired_idx])
|
||||
if idx != desired_idx:
|
||||
for i in range(idx, desired_idx, -1):
|
||||
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
|
||||
swap_tabs(self.os_window_id, i, i-1)
|
||||
self.swap_tabs(i, i-1)
|
||||
idx = desired_idx
|
||||
self._set_active_tab(idx)
|
||||
self.mark_tab_bar_dirty()
|
||||
|
|
@ -1540,100 +1539,117 @@ def previous_active_tab() -> Tab | None:
|
|||
@property
|
||||
def tab_bar_data(self) -> Sequence[TabBarData]:
|
||||
at = self.active_tab
|
||||
state = self.tab_drag_state
|
||||
dropped_tab_idx = dragged_tab_id = -1
|
||||
tab_being_dragged_from_here = False
|
||||
if state is not None and state.drag_started:
|
||||
dragged_tab_id = state.tab_id
|
||||
tab_being_dragged_from_here = True
|
||||
if self.tab_being_dropped is None and not tab_being_dragged_from_here:
|
||||
dragged_tab_id, drag_started = get_tab_being_dragged()[:2]
|
||||
if drag_started:
|
||||
tab_being_dragged_from_here = self.tab_for_id(dragged_tab_id) is not None
|
||||
if self.tab_being_dropped is None:
|
||||
if tab_being_dragged_from_here:
|
||||
return tuple(t.data_for_tab_bar(t is at) for t in self.tabs_to_be_shown_in_tab_bar if t.id != dragged_tab_id)
|
||||
return tuple(t.data_for_tab_bar(t is at) for t in self.tabs_to_be_shown_in_tab_bar)
|
||||
if self.tab_being_dropped is not None and self.tab_being_dropped.os_window_id == self.os_window_id:
|
||||
dropped_tab_idx = self.tab_being_dropped.drop_idx
|
||||
if dropped_tab_idx < 0:
|
||||
return tuple(t.data_for_tab_bar(t is at) for t in self.tabs_to_be_shown_in_tab_bar if t.id != dragged_tab_id)
|
||||
assert self.tab_being_dropped is not None
|
||||
ans: list[TabBarData] = []
|
||||
drop_idx = -1
|
||||
for i, tab in enumerate(self.tabs_to_be_shown_in_tab_bar):
|
||||
if i == dropped_tab_idx:
|
||||
drop_idx = len(ans)
|
||||
ans.append(self.tab_being_dropped)
|
||||
if tab.id != dragged_tab_id:
|
||||
ans.append(tab.data_for_tab_bar(at is tab))
|
||||
if drop_idx < 0:
|
||||
drop_idx = len(ans)
|
||||
ans.append(self.tab_being_dropped)
|
||||
if (tgt := self.tab_being_dropped.pending_drop_idx) > -1:
|
||||
self.tab_being_dropped = self.tab_being_dropped._replace(pending_drop_idx=-1)
|
||||
tgt = max(0, min(tgt, len(ans)-1))
|
||||
if tgt != drop_idx:
|
||||
ans[drop_idx], ans[tgt] = ans[tgt], ans[drop_idx]
|
||||
tmap = {t.id:t for t in self.tabs}
|
||||
at = self.active_tab
|
||||
ans = []
|
||||
for tid in self.tab_being_dropped.tab_ids:
|
||||
if tid == dragged_tab_id:
|
||||
ans.append(self.tab_being_dropped.data)
|
||||
else:
|
||||
tab = tmap[tid]
|
||||
ans.append(tab.data_for_tab_bar(tab is at))
|
||||
return ans
|
||||
|
||||
def on_tab_drop_move(self, tab_data: TabBarData, x: int, y: int) -> None:
|
||||
if tab_data.os_window_id == self.os_window_id:
|
||||
tid = self.tab_bar.tab_id_at(x)
|
||||
all_tabs = tuple(t.tab_id for t in self.tab_bar.last_laid_out_tabs)
|
||||
try:
|
||||
idx = all_tabs.index(tid)
|
||||
except ValueError:
|
||||
idx = -1
|
||||
if idx < 0:
|
||||
idx = len(all_tabs) if x > 20 else 0
|
||||
if self.tab_being_dropped is None:
|
||||
tab_data = tab_data._replace(drop_idx=idx)
|
||||
self.last_drop_move_x = x
|
||||
else:
|
||||
if x == self.last_drop_move_x:
|
||||
return
|
||||
mouse_moved_left = x < self.last_drop_move_x
|
||||
self.last_drop_move_x = x
|
||||
tab_data = tab_data._replace(pending_drop_idx=idx)
|
||||
cur_tab_data, self.tab_being_dropped = self.tab_being_dropped, tab_data
|
||||
new_tabs = tuple(t.tab_id for t in self.tab_bar_data)
|
||||
self.tab_being_dropped = cur_tab_data
|
||||
if new_tabs == all_tabs:
|
||||
return
|
||||
try:
|
||||
old_idx = all_tabs.index(tab_data.tab_id)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
new_idx = new_tabs.index(tab_data.tab_id)
|
||||
tab_moved_left = new_idx < old_idx
|
||||
if mouse_moved_left != tab_moved_left or new_idx == old_idx:
|
||||
return
|
||||
tab_data = tab_data._replace(pending_drop_idx=idx)
|
||||
self.tab_being_dropped = tab_data
|
||||
self.layout_tab_bar()
|
||||
elif self.tab_being_dropped is not None:
|
||||
self.tab_being_dropped = None
|
||||
def apply_tab_ordering(self, tab_ids: Sequence[int]) -> None:
|
||||
id_map = {t.id:t for t in self.tabs}
|
||||
ordered_ids = frozenset(tab_ids)
|
||||
positions = (i for i, t in enumerate(self.tabs) if t.id in ordered_ids)
|
||||
for pos, tab_id in zip(positions, tab_ids):
|
||||
self.tabs[pos] = id_map[tab_id]
|
||||
reorder_tabs(self.os_window_id, *(t.id for t in self.tabs))
|
||||
|
||||
def on_tab_drop_move(self, tab_id: int, is_dest: bool, x: int, y: int) -> None:
|
||||
if not is_dest:
|
||||
if self.tab_being_dropped:
|
||||
self.tab_being_dropped = None
|
||||
self.layout_tab_bar()
|
||||
return
|
||||
all_tabs = [t.tab_id for t in self.tab_bar.last_laid_out_tabs]
|
||||
if self.tab_being_dropped is None:
|
||||
tab = get_boss().tab_for_id(tab_id)
|
||||
if tab is None:
|
||||
return
|
||||
tab_data = tab.data_for_tab_bar(tab is get_boss().active_tab)
|
||||
if tab_id not in all_tabs:
|
||||
all_tabs.append(tab_id)
|
||||
_, _, start_x, _ = get_tab_being_dragged()
|
||||
self.tab_being_dropped = TabBeingDropped(data=tab_data, tab_ids=all_tabs, last_drop_move_x=int(start_x))
|
||||
mouse_moved_left = False
|
||||
if x == self.tab_being_dropped.last_drop_move_x:
|
||||
return
|
||||
mouse_moved_left = x < self.tab_being_dropped.last_drop_move_x
|
||||
old_tab_ids = self.tab_being_dropped.tab_ids
|
||||
idx_under_mouse = -1
|
||||
if (tab_id_under_mouse := self.tab_bar.tab_id_at(x)):
|
||||
with suppress(Exception):
|
||||
idx_under_mouse = old_tab_ids.index(tab_id_under_mouse)
|
||||
if idx_under_mouse < 0:
|
||||
idx_under_mouse = 0 if x < 20 else len(old_tab_ids) - 1
|
||||
old_idx_under_mouse = old_tab_ids.index(tab_id)
|
||||
idx_moved_left = old_idx_under_mouse > idx_under_mouse
|
||||
new_tab_ids = old_tab_ids
|
||||
if mouse_moved_left == idx_moved_left:
|
||||
new_tab_ids = list(old_tab_ids)
|
||||
new_tab_ids[idx_under_mouse], new_tab_ids[old_idx_under_mouse] = new_tab_ids[old_idx_under_mouse], new_tab_ids[idx_under_mouse]
|
||||
self.tab_being_dropped = self.tab_being_dropped._replace(last_drop_move_x=x, tab_ids=new_tab_ids)
|
||||
if self.tab_being_dropped.tab_ids != old_tab_ids:
|
||||
self.layout_tab_bar()
|
||||
|
||||
def start_tab_drag(self, pixels: bytes, width: int, height: int) -> None:
|
||||
if (state := self.tab_drag_state) is None:
|
||||
def on_tab_drop(self, x: int, y: int) -> None:
|
||||
if (td := self.tab_being_dropped) is None:
|
||||
return
|
||||
if (tab := get_boss().tab_for_id(td.data.tab_id)) is None:
|
||||
return
|
||||
self.on_tab_drop_move(td.data.tab_id, True, x, y)
|
||||
if (td := self.tab_being_dropped) is None:
|
||||
return
|
||||
self.tab_being_dropped = None
|
||||
atid = self.active_tab.id if self.active_tab else 0
|
||||
if tab.os_window_id != self.os_window_id:
|
||||
get_boss()._move_tab_to(tab, self.os_window_id)
|
||||
self.apply_tab_ordering(td.tab_ids)
|
||||
if atid and tab.os_window_id == self.os_window_id and (tab := self.tab_for_id(atid)):
|
||||
idx = self.tabs.index(tab)
|
||||
self._set_active_tab(idx, store_in_history=False)
|
||||
self.layout_tab_bar()
|
||||
|
||||
def swap_tabs(self, idx: int, nidx: int) -> None:
|
||||
if idx != nidx:
|
||||
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
|
||||
swap_tabs(self.os_window_id, idx, nidx)
|
||||
|
||||
def start_tab_drag(self, pixels: bytes, width: int, height: int) -> None:
|
||||
dragged_tab_id = get_tab_being_dragged()[0]
|
||||
for i, tab in enumerate(self.tabs_to_be_shown_in_tab_bar):
|
||||
if tab.id == state.tab_id:
|
||||
td = tab.data_for_tab_bar(tab is self.active_tab)._replace(
|
||||
is_being_moved=True, drop_idx=i
|
||||
)
|
||||
if tab.id == dragged_tab_id:
|
||||
td = tab.data_for_tab_bar(tab is self.active_tab)
|
||||
title = apply_title_template(self.tab_bar.draw_data, td, i+1)
|
||||
title = re.sub(r'\x1b\[.+?[a-zA-Z]', '', title).strip() # strip CSI codes
|
||||
title = re.sub(r'\x1b\[.+?[a-zA-Z]', '', title).strip() # strip CSI codes ]
|
||||
title = replace_c0_codes_except_nl_space_tab(title.encode()).decode()
|
||||
title = re.sub(r'\n', ' ', title)
|
||||
# TODO: Add the title to the drag icon
|
||||
drag_data = {
|
||||
f'application/net.kovidgoyal.kitty-tab-{os.getpid()}': str(tab.id).encode(),
|
||||
}
|
||||
start_drag_with_data(self.os_window_id, drag_data, pixels, width, height)
|
||||
self.tab_drag_state = state._replace(tab_being_dragged=td)
|
||||
break
|
||||
else:
|
||||
set_tab_being_dragged()
|
||||
|
||||
def handle_tab_bar_mouse(self, x: float, y: float, button: int, modifiers: int, action: int) -> None:
|
||||
if button == -1: # motion
|
||||
if (state := self.tab_drag_state) is not None and not state.drag_started:
|
||||
if math.sqrt((x-state.start_x)**2 + (y-state.start_y)**2) > 5:
|
||||
self.tab_drag_state = state._replace(drag_started=True)
|
||||
dragged_tab_id, drag_started, start_x, start_y = get_tab_being_dragged()
|
||||
if dragged_tab_id and self.tab_for_id(dragged_tab_id) is not None and not drag_started:
|
||||
if math.sqrt((x-start_x)**2 + (y-start_y)**2) > 5:
|
||||
set_tab_being_dragged(dragged_tab_id, True, start_x, start_y)
|
||||
request_callback_with_thumbnail("start_tab_drag", self.os_window_id)
|
||||
return
|
||||
|
||||
|
|
@ -1654,14 +1670,15 @@ def handle_tab_bar_mouse(self, x: float, y: float, button: int, modifiers: int,
|
|||
return
|
||||
else:
|
||||
if button == GLFW_MOUSE_BUTTON_LEFT:
|
||||
global ignore_left_button_release
|
||||
if action == GLFW_PRESS:
|
||||
if (idx := self.tabs.index(tab) if tab in self.tabs else -1) > -1:
|
||||
set_tab_being_dragged(tab.id)
|
||||
self.tab_drag_state = TabDragState(
|
||||
tab_id=tab.id, start_x=x, start_y=y, original_index=idx)
|
||||
set_tab_being_dragged(tab.id, False, x, y)
|
||||
ignore_left_button_release = True
|
||||
else:
|
||||
if self.tab_drag_state is None or not self.tab_drag_state.drag_started:
|
||||
ignore, ignore_left_button_release = ignore_left_button_release, False
|
||||
if not ignore:
|
||||
self.set_active_tab(tab)
|
||||
set_tab_being_dragged()
|
||||
elif button == GLFW_MOUSE_BUTTON_MIDDLE:
|
||||
if action == GLFW_RELEASE and self.recent_mouse_events:
|
||||
p = self.recent_mouse_events[-1]
|
||||
|
|
|
|||
Loading…
Reference in a new issue