Compare commits

..

19 Commits
curve ... f2

Author SHA1 Message Date
Kovid Goyal
715645e69c More work on floats 2025-05-11 22:06:12 +05:30
Kovid Goyal
4e697abb34 more work on floats 2025-05-11 22:06:12 +05:30
Kovid Goyal
96b0c463e8 Start work on floating windows 2025-05-11 22:06:11 +05:30
Kovid Goyal
f2412cc9c8 version 0.42.0 2025-05-11 10:42:15 +05:30
Kovid Goyal
5dbd198fb9 Update changelog 2025-05-11 10:36:23 +05:30
Kovid Goyal
9b4643e8bc ... 2025-05-11 07:46:06 +05:30
Kovid Goyal
f2f914ed05 More natural limits for step size when sampling paramterized curve 2025-05-10 22:58:56 +05:30
Kovid Goyal
ec5062ceb4 Merge branch 'master' of https://github.com/nomisreual/kitty 2025-05-10 21:23:35 +05:30
Simon Antonius Lauer
eb70f81218 fix: add missing pkgs and manually add font for nix shell 2025-05-10 14:42:46 +02:00
Kovid Goyal
04a4d62859 Replace all remaining uses of old parametrized drawing code 2025-05-10 11:07:26 +05:30
Kovid Goyal
2aaa440519 Move calc of line width out of draw_parametrized_curve 2025-05-10 10:12:57 +05:30
Kovid Goyal
782e7cb2fb ... 2025-05-10 10:03:29 +05:30
Kovid Goyal
41b44fdafa Prepare for moving circle rendering to new code as well 2025-05-10 09:28:20 +05:30
Kovid Goyal
0f3603c0eb Use new parametrized curve rendering for rounded separators 2025-05-10 09:23:12 +05:30
Kovid Goyal
8ba9bfd460 Prepare for using new parametrized drawing code for beziers as well 2025-05-10 09:06:44 +05:30
Kovid Goyal
22fd548903 Also adjust in y direction for odd height 2025-05-10 09:00:57 +05:30
Kovid Goyal
7856046382 Proper odd width x-offset adjustment 2025-05-10 08:56:33 +05:30
Kovid Goyal
f93d57d919 Mark curve data as const 2025-05-10 08:50:29 +05:30
Kovid Goyal
e8d50d0734 Finish up implementation of drawing curve with derivative
Fixes #8299
2025-05-10 08:38:32 +05:30
19 changed files with 357 additions and 210 deletions

View File

@@ -106,12 +106,12 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.42.0 [future]
0.42.0 [2025-05-11]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new kitten: :doc:`quick-access-terminal </kittens/quick-access-terminal>` to :ref:`quake`
- The :doc:`panel kitten </kittens/panel>` now works on macOS as well as Wayland (:iss:`2590`)
- The :doc:`panel kitten </kittens/panel>` works on macOS and X11 as well as Wayland (:iss:`2590`)
- **Behavior change**: Now kitty does full grapheme segmentation following the
Unicode 16 spec when splitting text into cells (:iss:`8533`)
@@ -123,7 +123,7 @@ Detailed list of changes
- launch: Allow creating desktop panels such as those created by the :doc:`panel kitten </kittens/panel>` (:iss:`8549`)
- Remote control: Allow modifying desktop panels and showing/hiding OS Windows
using the `kitten @ resize-os-window` command (:iss:`8550`)
using the ``kitten @ resize-os-window`` command (:iss:`8550`)
- Remote control launch: Allow waiting for a program launched in a new window
to exit and get the exit code via the `kitty +launch
@@ -160,7 +160,7 @@ Detailed list of changes
- :ac:`change_font_size` allow multiplying/dividing the current font size in addition to incrementing it (:iss:`8616`)
- Box drawing: Improve appearance of rounder corners giving them a uniform line width (:iss:`8299`)
- Box drawing: Improve appearance of rounder corners, giving them a uniform line width (:iss:`8299`)
0.41.1 [2025-04-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -91,7 +91,7 @@ class Borders:
for tbr in tab_bar_rects:
add_borders_rect(self.os_window_id, self.tab_id, *tbr)
bw = 0
groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True))
groups = tuple(all_windows.iter_all_layoutable_groups())
if groups:
bw = groups[0].effective_border()
draw_borders = bw > 0 and draw_window_borders

View File

@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 41, 1)
version: Version = Version(0, 42, 0)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -592,16 +592,27 @@ typedef struct CubicBezier {
} CubicBezier;
#define bezier_eq(which) { \
double tm1 = 1 - t; \
double tm1_3 = tm1 * tm1 * tm1; \
double t_3 = t * t * t; \
return tm1_3 * cb.start.which + 3 * t * tm1 * (tm1 * cb.c1.which + t * cb.c2.which) + t_3 * cb.end.which; \
const CubicBezier *cb = v; \
const double u = 1. - t; \
const double u_3 = u * u * u; \
const double t_3 = t * t * t; \
return u_3 * cb->start.which + 3 * t * u * (u * cb->c1.which + t * cb->c2.which) + t_3 * cb->end.which; \
}
static double
bezier_x(CubicBezier cb, double t) { bezier_eq(x); }
static double
bezier_y(CubicBezier cb, double t) { bezier_eq(y); }
#define bezier_prime_eq(which) { \
const CubicBezier *cb = v; \
const double u = 1. - t; \
const double u_2 = u * u; \
const double t_2 = t * t; \
return 3 * u_2 * (cb->c1.which - cb->start.which) + 6 * t * u * (cb->c2.which - cb->c1.which) + 3 * t_2 * (cb->end.which - cb->c2.which); \
}
static double bezier_x(const void *v, double t) { bezier_eq(x); }
static double bezier_y(const void *v, double t) { bezier_eq(y); }
static double bezier_prime_x(const void *v, double t) { bezier_prime_eq(x); }
static double bezier_prime_y(const void *v, double t) { bezier_prime_eq(y); }
#undef bezier_eq
#undef bezier_prime_eq
static int
find_bezier_for_D(int width, int height) {
@@ -609,13 +620,13 @@ find_bezier_for_D(int width, int height) {
CubicBezier cb = {.end={.x=0, .y=height - 1}, .c2={.x=0, .y=height - 1}};
while (true) {
cb.c1.x = cx; cb.c2.x = cx;
if (bezier_x(cb, 0.5) > width - 1) return last_cx;
if (bezier_x(&cb, 0.5) > width - 1) return last_cx;
last_cx = cx++;
}
}
static double
find_t_for_x(CubicBezier cb, int x, double start_t) {
find_t_for_x(const CubicBezier *cb, int x, double start_t) {
if (fabs(bezier_x(cb, start_t) - x) < 0.1) return start_t;
static const double t_limit = 0.5;
double increment = t_limit - start_t;
@@ -639,7 +650,7 @@ find_t_for_x(CubicBezier cb, int x, double start_t) {
static void
get_bezier_limits(Canvas *self, CubicBezier cb) {
get_bezier_limits(Canvas *self, const CubicBezier *cb) {
int start_x = (int)bezier_x(cb, 0), max_x = (int)bezier_x(cb, 0.5);
double last_t = 0.;
for (int x = start_x; x < max_x + 1; x++) {
@@ -670,40 +681,11 @@ static void
filled_D(Canvas *self, bool left) {
int c1x = find_bezier_for_D(self->width, self->height);
CubicBezier cb = {.end={.y=self->height-1}, .c1 = {.x=c1x}, .c2 = {.x=c1x, .y=self->height - 1}};
get_bezier_limits(self, cb);
get_bezier_limits(self, &cb);
if (left) fill_region(self, false);
else mirror_horizontally(fill_region(self, false));
}
#define NAME position_set
#define KEY_TY Point
#define HASH_FN hash_point
#define CMPR_FN cmpr_point
static uint64_t hash_point(Point p);
static bool cmpr_point(Point, Point);
#include "kitty-verstable.h"
static uint64_t hash_point(Point p) { return vt_hash_integer(p.val); }
static bool cmpr_point(Point a, Point b) { return a.val == b.val; }
#define draw_parametrized_curve(self, level, xfunc, yfunc) { \
div_t d = div(thickness(self, level, true), 2u); \
int delta = d.quot, extra = d.rem; \
uint num_samples = self->height * 8; \
position_set seen; vt_init(&seen); \
for (uint i = 0; i < num_samples + 1; i++) { \
double t = i / (double)num_samples; \
Point p = {.x=(int32_t)xfunc, .y=(int32_t)yfunc}; \
position_set_itr q = vt_get(&seen, p); \
if (!vt_is_end(q)) continue; \
if (vt_is_end(vt_insert(&seen, p))) fatal("Out of memory"); \
for (int y = MAX(0, p.y - delta); y < MIN(p.y + delta + extra, (int)self->height); y++) { \
uint offset = y * self->width, start = MAX(0, p.x - delta); \
memset(self->mask + offset + start, 255, minus((uint)MIN(p.x + delta + extra, (int)self->width), start)); \
} \
} \
vt_cleanup(&seen); \
}
static double
distance(double x1, double y1, double x2, double y2) {
const double dx = x1 - x2;
@@ -711,27 +693,28 @@ distance(double x1, double y1, double x2, double y2) {
return sqrt(dx * dx + dy * dy);
}
typedef double(*curve_func)(void *, double t);
typedef double(*curve_func)(const void *, double t);
static void
draw_parametrized_curve_with_derivative(
Canvas *self, void *curve_data, uint level, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
int x_offset, double thickness_fudge
Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
int x_offset, int yoffset, double thickness_fudge
) {
const double th = thickness_as_float(self, level, true);
double step = 1.0 / self->height;
const double min_step = 1.0 / (100 * self->height);
const double half_thickness = th / 2.0;
double larger_dim = fmax(self->height, self->width);
double step = 1.0 / larger_dim;
const double min_step = step / 100., max_step = step;
line_width = fmax(1., line_width);
const double half_thickness = line_width / 2.0;
const double distance_limit = half_thickness + thickness_fudge;
double t = 0;
while(true) {
double x = xfunc(curve_data, t), y = yfunc(curve_data, t);
for (double dy = -th; dy <= th; dy++) {
for (double dx = -th; dx <= th; dx++) {
for (double dy = -line_width; dy <= line_width; dy++) {
for (double dx = -line_width; dx <= line_width; dx++) {
double px = x + dx, py = y + dy;
double dist = distance(x, y, px, py);
int row = (int)py, col = (int)px + x_offset;
if (dist >= distance_limit || row >= (int)self->height || row < 0 || col >= (int)self->width || col < 0) continue;
int row = (int)py + yoffset, col = (int)px + x_offset;
if (dist > distance_limit || row >= (int)self->height || row < 0 || col >= (int)self->width || col < 0) continue;
const int offset = row * self->width + col;
double alpha = 1.0 - (dist / half_thickness);
uint8_t old_alpha = self->mask[offset];
@@ -743,7 +726,7 @@ draw_parametrized_curve_with_derivative(
double dx = x_prime(curve_data, t), dy = y_prime(curve_data, t);
double d = sqrt(dx * dx + dy * dy);
step = 1.0 / fmax(1e-6, d);
step = fmax(min_step, fmin(step, 0.01));
step = fmax(min_step, fmin(step, max_step));
t = fmin(t + step, 1.0);
}
}
@@ -753,8 +736,10 @@ rounded_separator(Canvas *self, uint level, bool left) {
uint gap = thickness(self, level, true);
int c1x = find_bezier_for_D(minus(self->width, gap), self->height);
CubicBezier cb = {.end={.y=self->height - 1}, .c1={.x=c1x}, .c2={.x=c1x, .y=self->height - 1}};
if (left) { draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t)); }
else { mirror_horizontally(draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t))); }
double line_width = thickness_as_float(self, level, true);
#define d draw_parametrized_curve_with_derivative(self, &cb, line_width, bezier_x, bezier_y, bezier_prime_x, bezier_prime_y, 0, 0, 0)
if (left) { d; } else { mirror_horizontally(d); }
#undef d
}
static void
@@ -772,56 +757,61 @@ corner_triangle(Canvas *self, const Corner corner) {
}
typedef struct Circle {
Point origin;
double radius;
double x, y, radius;
double start, end, amt;
} Circle;
static Circle
circle(Point origin, double radius, double start_at, double end_at) {
circle(double x, double y, double radius, double start_at, double end_at) {
double conv = M_PI / 180.;
Circle ans = {.origin=origin, .radius=radius, .start=start_at*conv, .end=end_at*conv};
Circle ans = {.x=x, .y=y, .radius=radius, .start=start_at*conv, .end=end_at*conv};
ans.amt = ans.end - ans.start;
return ans;
}
static double
circle_x(Circle c, double t) { return c.origin.x + c.radius * cos(c.start + c.amt * t); }
static double
circle_y(Circle c, double t) { return c.origin.y + c.radius * sin(c.start + c.amt * t); }
static double circle_x(const void *v, double t) { const Circle *c=v; return c->x + c->radius * cos(c->start + c->amt * t); }
static double circle_y(const void *v, double t) { const Circle *c=v; return c->y + c->radius * sin(c->start + c->amt * t); }
static double circle_prime_x(const void *v, double t) { const Circle *c=v; return -c->radius * sin(c->start + c->amt * t); }
static double circle_prime_y(const void *v, double t) { const Circle *c=v; return c->radius * cos(c->start + c->amt * t); }
static void
spinner(Canvas *self, uint level, double start_degrees, double end_degrees) {
uint w = self->width / 2, h = self->height / 2;
uint radius = minus(min(w, h), thickness(self, level, true) / 2);
Circle c = circle((Point){.x=w, .y=h}, radius, start_degrees, end_degrees);
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
double x = self->width / 2.0, y = self->height / 2.0;
double line_width = thickness_as_float(self, level, true);
double radius = fmax(0, fmin(x, y) - line_width / 2.0);
Circle c = circle(x, y, radius, start_degrees, end_degrees);
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
}
static void
draw_circle(Canvas *self, double scale, double gap, bool invert) {
const uint w = self->width / 2, h = self->height / 2;
const double radius = (int)(scale * min(w, h) - gap / 2);
const uint8_t fill = invert ? 0 : 255;
fill_circle_of_radius(Canvas *self, double origin_x, double origin_y, double radius, uint8_t alpha) {
const double limit = radius * radius;
for (uint y = 0; y < self->height; y++) {
for (uint x = 0; x < self->width; x++) {
double xw = (double)x - w, yh = (double)y - h;
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = fill;
double xw = (double)x - origin_x, yh = (double)y - origin_y;
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = alpha;
}
}
}
static void
draw_fish_eye(Canvas *self, uint level) {
uint w = self->width / 2, h = self->height / 2;
uint line_width = thickness(self, level, true) / 2;
uint radius = minus(min(w, h), line_width);
Circle c = circle((Point){.x=w, .y=h}, radius, 0, 360);
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
uint gap = minus(radius, radius / 10);
draw_circle(self, 1.0, gap, false);
fill_circle(Canvas *self, double scale, double gap, bool invert) {
const uint w = self->width / 2, h = self->height / 2;
const double radius = (int)(scale * min(w, h) - gap / 2);
const uint8_t fill = invert ? 0 : 255;
fill_circle_of_radius(self, w, h, radius, fill);
}
static void
draw_fish_eye(Canvas *self, uint level UNUSED) {
double x = self->width / 2., y = self->height / 2.;
double radius = fmin(x, y);
double central_radius = (2./3.) * radius;
fill_circle_of_radius(self, x, y, central_radius, 255);
double line_width = fmax(1. * self->supersample_factor, (radius - central_radius) / 2.5);
radius = fmax(0, fmin(x, y) - line_width / 2.);
Circle c = circle(x, y, radius, 0, 360);
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
}
static void
@@ -1242,28 +1232,28 @@ typedef struct Rectircle {
} Rectircle;
static double
rectircle_x(void *v, double t) {
Rectircle *r = v;
rectircle_x(const void *v, double t) {
const Rectircle *r = v;
return r->x_start + r->x_sign * r->a * pow(cos(t * (M_PI / 2.0)), r->xexp);
}
static double
rectircle_x_prime(void *v, double t) {
Rectircle *r = v;
rectircle_x_prime(const void *v, double t) {
const Rectircle *r = v;
t *= (M_PI / 2.0);
return r->x_prime_coeff * pow(cos(t), r->x_prime_exp) * sin(t);
}
static double
rectircle_y_prime(void *v, double t) {
Rectircle *r = v;
rectircle_y_prime(const void *v, double t) {
const Rectircle *r = v;
t *= (M_PI / 2.0);
return r->y_prime_coeff * pow(sin(t), r->y_prime_exp) * cos(t);
}
static double
rectircle_y(void *v, double t) {
Rectircle *r = v;
rectircle_y(const void *v, double t) {
const Rectircle *r = v;
return r->y_start + r->y_sign * r->b * pow(sin(t * (M_PI / 2.0)), r->yexp);
}
@@ -1307,8 +1297,12 @@ rectcircle(Canvas *self, Corner which) {
static void
rounded_corner(Canvas *self, uint level, Corner which) {
Rectircle r = rectcircle(self, which);
int x_offset = -((self->width + 1) & 1); // line up with box drawing lines when width is even
draw_parametrized_curve_with_derivative(self, &r, level, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, 0.1);
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
uint cell_height_is_odd = (self->height / self->supersample_factor) & 1;
// adjust for odd cell dimensions to line up with box drawing lines
int x_offset = -(cell_width_is_odd & 1), y_offset = -(cell_height_is_odd & 1);
double line_width = thickness_as_float(self, level, true);
draw_parametrized_curve_with_derivative(self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset, 0.1);
}
static void
@@ -1319,8 +1313,8 @@ commit(Canvas *self, Edge lines, bool solid) {
if (lines & LEFT_EDGE) draw_hline(self, 0, hw, hh, level);
if (lines & TOP_EDGE) draw_vline(self, 0, hh, hw, level);
if (lines & BOTTOM_EDGE) draw_vline(self, hh, self->height, hw, level);
draw_circle(self, scale, 0, false);
if (!solid) draw_circle(self, scale, thickness(self, level, true), true);
fill_circle(self, scale, 0, false);
if (!solid) fill_circle(self, scale, thickness(self, level, true), true);
}
// thin and fat line levels
@@ -1577,7 +1571,7 @@ START_ALLOW_CASE_RANGE
S(L'', spinner, 1, 450, 540);
S(L'', spinner, 1, 180, 360);
S(L'', spinner, 1, 0, 180);
S(L'', draw_circle, 1.0, 0, false);
S(L'', fill_circle, 1.0, 0, false);
S(L'', draw_fish_eye, 0);
C(L'', dhline, 1, TOP_EDGE | BOTTOM_EDGE);

View File

@@ -526,7 +526,7 @@ def monitor_pid(pid: int) -> None:
pass
def add_window(os_window_id: int, tab_id: int, title: str) -> int:
def add_window(os_window_id: int, tab_id: int, title: str, is_floating: bool) -> int:
pass

View File

@@ -16,7 +16,7 @@ from .clipboard import set_clipboard_string, set_primary_selection
from .fast_data_types import add_timer, get_boss, get_options, get_os_window_title, patch_color_profiles
from .options.utils import env as parse_env
from .tabs import Tab, TabManager
from .types import LayerShellConfig, OverlayType, run_once
from .types import FloatType, LayerShellConfig, OverlayType, run_once
from .utils import get_editor, log_error, resolve_custom_file, which
from .window import CwdRequest, CwdRequestType, Watchers, Window
@@ -81,7 +81,7 @@ of the active window in the tab is used as the tab title. The special value
--type
type=choices
default=window
choices=window,tab,os-window,os-panel,overlay,overlay-main,background,clipboard,primary
choices=window,tab,os-window,os-panel,overlay,overlay-main,float-in-window,float-in-tab,background,clipboard,primary
Where to launch the child process:
:code:`window`
@@ -105,6 +105,14 @@ Where to launch the child process:
directory, the input text for kittens, launch commands, etc. Useful if this overlay is
intended to run for a long time as a primary window.
:code:`float-in-window`
A floating window that is drawn over a non-floating window, usually the currently
active window.
:code:`float-in-tab`
A floating window that is drawn over all windows in a tab, usually the currently
active tab.
:code:`background`
The process will be run in the :italic:`background`, without a kitty
window. Note that if :option:`kitten @ launch --allow-remote-control` is
@@ -753,8 +761,9 @@ def _launch(
tab = tab_for_window(boss, opts, target_tab, next_to)
watchers = load_watch_modules(opts.watcher)
with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus):
float_type = {'float-in-window': FloatType.window, 'float-in-tab': FloatType.tab}.get(opts.type, FloatType.none)
new_window: Window = tab.new_window(
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw)
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, float_type=float_type, **kw)
if child_death_callback is not None:
boss.monitor_pid(new_window.child.pid or 0, child_death_callback)
if spacing:

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from collections.abc import Generator, Iterable, Iterator, Sequence
from collections.abc import Generator, Iterable, Sequence
from functools import partial
from itertools import repeat
from typing import Any, NamedTuple
@@ -9,7 +9,7 @@ from typing import Any, NamedTuple
from kitty.borders import BorderColor
from kitty.fast_data_types import Region, set_active_window, viewport_for_window
from kitty.options.types import Options
from kitty.types import Edges, WindowGeometry
from kitty.types import Edges, FloatType, WindowGeometry
from kitty.typing_compat import TypedDict, WindowType
from kitty.window_list import WindowGroup, WindowList
@@ -241,9 +241,9 @@ class Layout:
def bias_increment_for_cell(self, all_windows: WindowList, is_horizontal: bool) -> float:
self._set_dimensions()
return self.calculate_bias_increment_for_a_single_cell(all_windows, is_horizontal)
return self.calculate_bias_increment_for_a_single_cell(is_horizontal)
def calculate_bias_increment_for_a_single_cell(self, all_windows: WindowList, is_horizontal: bool) -> float:
def calculate_bias_increment_for_a_single_cell(self, is_horizontal: bool) -> float:
if is_horizontal:
return (lgd.cell_width + 1) / lgd.central.width
return (lgd.cell_height + 1) / lgd.central.height
@@ -284,7 +284,7 @@ class Layout:
return self.neighbors_for_window(w, all_windows)
def move_window(self, all_windows: WindowList, delta: int = 1) -> bool:
if all_windows.num_groups < 2 or not delta:
if all_windows.num_of_non_floating_groups < 2 or not delta:
return False
return all_windows.move_window_group(by=delta)
@@ -332,7 +332,7 @@ class Layout:
def _bias_slot(self, all_windows: WindowList, idx: int, bias: float) -> bool:
fractional_bias = max(10, min(abs(bias), 90)) / 100
h, v = self.calculate_bias_increment_for_a_single_cell(all_windows, True), self.calculate_bias_increment_for_a_single_cell(all_windows, False)
h, v = self.calculate_bias_increment_for_a_single_cell(True), self.calculate_bias_increment_for_a_single_cell(False)
nh, nv = lgd.central.width / lgd.cell_width, lgd.central.height / lgd.cell_height
f = max(-90, min(bias, 90)) / 100.
return self.bias_slot(all_windows, idx, fractional_bias, h * nh *f, v * nv * f)
@@ -341,10 +341,22 @@ class Layout:
return False
def update_visibility(self, all_windows: WindowList) -> None:
active_window = all_windows.active_window
for window, is_group_leader in all_windows.iter_windows_with_visibility():
is_visible = window is active_window or (is_group_leader and not self.only_active_window_visible)
window.set_visible_in_layout(is_visible)
active_non_floating_window = all_windows.active_non_floating_window
floating_windows = []
vismap = {}
for window, is_group_leader, is_floating in all_windows.iter_windows_with_visibility():
if is_floating:
floating_windows.append((window, is_group_leader))
else:
is_visible = window is active_non_floating_window or (is_group_leader and not self.only_active_window_visible)
window.set_visible_in_layout(is_visible)
vismap[window.id] = is_visible
for window, is_group_leader in sorted(floating_windows, key=lambda x: x[0].id):
vis = is_group_leader
if vis and window.float_type is FloatType.window:
vis = vismap.get(window.floating_in_window, True) # if floating_in_window not found float in tab
window.set_visible_in_layout(vis)
vismap[window.id] = vis
def _set_dimensions(self) -> None:
lgd.central, tab_bar, vw, vh, lgd.cell_width, lgd.cell_height = viewport_for_window(self.os_window_id)
@@ -354,6 +366,7 @@ class Layout:
self.update_visibility(all_windows)
self.blank_rects = []
self.do_layout(all_windows)
self.layout_floats(all_windows)
def layout_single_window_group(self, wg: WindowGroup, add_blank_rects: bool = True) -> None:
bw = 1 if self.must_draw_borders else 0
@@ -372,7 +385,7 @@ class Layout:
def xlayout(
self,
groups: Iterator[WindowGroup],
groups: Iterable[WindowGroup],
bias: None | Sequence[float] | dict[int, float] = None,
start: int | None = None,
size: int | None = None,
@@ -391,7 +404,7 @@ class Layout:
def ylayout(
self,
groups: Iterator[WindowGroup],
groups: Iterable[WindowGroup],
bias: None | Sequence[float] | dict[int, float] = None,
start: int | None = None,
size: int | None = None,
@@ -417,6 +430,11 @@ class Layout:
def do_layout(self, windows: WindowList) -> None:
raise NotImplementedError()
def layout_floats(self, windows: WindowList) -> None:
for gr in windows.iter_all_floating_groups():
# TODO: actually position the float based on its properties
self.layout_single_window_group(gr, False)
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap:
return {'left': [], 'right': [], 'top': [], 'bottom': []}

View File

@@ -75,7 +75,7 @@ class Grid(Layout):
return 0, 0
def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool:
num_windows = all_windows.num_groups
num_windows = all_windows.num_layoutable_groups
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col)
if row_num == 0:
@@ -93,7 +93,7 @@ class Grid(Layout):
return tuple(self.variable_layout(layout_func, num_windows, b)) == before_layout
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
num_windows = all_windows.num_groups
num_windows = all_windows.num_layoutable_groups
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
row_num, col_num = self.position_for_window_idx(idx, num_windows, ncols, nrows, special_rows, special_col)
@@ -151,7 +151,7 @@ class Grid(Layout):
on_col_done(col_windows)
def do_layout(self, all_windows: WindowList) -> None:
n = all_windows.num_groups
n = all_windows.num_layoutable_groups
if n == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
@@ -193,7 +193,7 @@ class Grid(Layout):
position_window_in_grid_cell(window_idx, xl, yl)
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
n = all_windows.num_groups
n = all_windows.num_layoutable_groups
if not lgd.draw_minimal_borders or n < 2:
return
needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders)
@@ -260,7 +260,7 @@ class Grid(Layout):
yield BorderLine(edges, color)
def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
n = all_windows.num_groups
n = all_windows.num_layoutable_groups
if n < 4:
return neighbors_for_tall_window(1, window, all_windows)

View File

@@ -118,9 +118,10 @@ class Tall(Layout):
return True
def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension:
num = all_windows.num_groups - self.num_full_size_windows
windows = tuple(all_windows.iter_all_layoutable_groups())
num = len(windows) - self.num_full_size_windows
bias = biased_map if num > 1 else None
return self.perp_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias, offset=self.num_full_size_windows)
return self.perp_axis_layout(windows, bias=bias, offset=self.num_full_size_windows)
def bias_slot(self, all_windows: WindowList, idx: int, fractional_bias: float, cell_increment_bias_h: float, cell_increment_bias_v: float) -> bool:
if idx < len(self.main_bias):
@@ -134,7 +135,7 @@ class Tall(Layout):
return before_layout == after_layout
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
num_windows = all_windows.num_groups
num_windows = all_windows.num_layoutable_groups
if self.main_is_horizontal == is_horizontal:
before_main_bias = self.main_bias
ncols = self.num_full_size_windows + 1
@@ -159,10 +160,10 @@ class Tall(Layout):
return before != after
def simple_layout(self, all_windows: WindowList) -> Generator[tuple[WindowGroup, LayoutData, LayoutData, bool], None, None]:
num = all_windows.num_groups
is_fat = not self.main_is_horizontal
mirrored = self.layout_opts.mirrored
groups = tuple(all_windows.iter_all_layoutable_groups())
num = len(groups)
main_bias = self.main_bias[::-1] if mirrored else self.main_bias
if mirrored:
groups = tuple(reversed(groups))
@@ -218,9 +219,10 @@ class Tall(Layout):
yield wg, xl, yl, False
def do_layout(self, all_windows: WindowList) -> None:
num = all_windows.num_groups
windows = tuple(all_windows.iter_all_layoutable_groups())
num = len(windows)
if num == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
self.layout_single_window_group(windows[0])
return
layouts = (self.simple_layout if num <= self.num_full_size_windows + 1 else self.full_layout)(all_windows)
for wg, xl, yl, is_full_size in layouts:
@@ -270,7 +272,7 @@ class Tall(Layout):
return None
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
num = all_windows.num_groups
num = all_windows.num_layoutable_groups
if num < 2 or not lgd.draw_minimal_borders:
return
try:

View File

@@ -68,9 +68,10 @@ class Vertical(Layout):
perp_axis_layout = Layout.xlayout
def variable_layout(self, all_windows: WindowList, biased_map: dict[int, float]) -> LayoutDimension:
num_windows = all_windows.num_groups
windows = tuple(all_windows.iter_all_layoutable_groups())
num_windows = len(windows)
bias = biased_map if num_windows > 1 else None
return self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias)
return self.main_axis_layout(windows, bias=bias)
def fixed_layout(self, wg: WindowGroup) -> LayoutDimension:
return self.perp_axis_layout(iter((wg,)), border_mult=0 if lgd.draw_minimal_borders else 1)
@@ -82,7 +83,7 @@ class Vertical(Layout):
def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
if self.main_is_horizontal != is_horizontal:
return False
num_windows = all_windows.num_groups
num_windows = all_windows.num_layoutable_groups
if num_windows < 2:
return False
before_layout = list(self.variable_layout(all_windows, self.biased_map))
@@ -109,7 +110,7 @@ class Vertical(Layout):
yield wg, xl, yl
def do_layout(self, all_windows: WindowList) -> None:
window_count = all_windows.num_groups
window_count = all_windows.num_layoutable_groups
if window_count == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
@@ -117,7 +118,7 @@ class Vertical(Layout):
self.set_window_group_geometry(wg, xl, yl)
def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]:
window_count = all_windows.num_groups
window_count = all_windows.num_layoutable_groups
if window_count < 2 or not lgd.draw_minimal_borders:
return
yield from borders(self.generate_layout_data(all_windows), self.main_is_horizontal, all_windows)

View File

@@ -1134,7 +1134,7 @@ draw_cells(ssize_t vao_idx, const WindowRenderData *srd, OSWindow *os_window, bo
}
bool use_premult = false;
has_underlying_image |= grd.num_of_below_refs > 0 || grd.num_of_negative_refs > 0;
if (os_window->is_semi_transparent) {
if (os_window->is_semi_transparent || srd->float_data.is_floating) {
if (has_underlying_image) { draw_cells_interleaved_premult(vao_idx, screen, os_window, &crd, grd, wl); use_premult = true; }
else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent);
} else {

View File

@@ -7,6 +7,7 @@
#include "cleanup.h"
#include "options/to-c-generated.h"
#include "iqsort.h"
#include <math.h>
#include <sys/mman.h>
@@ -288,10 +289,11 @@ set_window_logo(Window *w, const char *path, const ImageAnchorPosition pos, floa
}
static void
initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
initialize_window(Window *w, PyObject *title, bool init_gpu_resources, bool is_floating) {
w->id = ++global_state.window_id_counter;
w->visible = true;
w->title = title;
w->render_data.float_data.is_floating = is_floating;
Py_XINCREF(title);
if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) {
log_error("Failed to load default window logo: %s", OPT(default_window_logo));
@@ -303,14 +305,23 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
}
}
static void
sort_windows_in_render_order(Window *windows, size_t count) {
#define lt(a, b) (!a->render_data.float_data.is_floating && b->render_data.float_data.is_floating) || (a->render_data.float_data.is_floating == b->render_data.float_data.is_floating && a->id < b->id)
QSORT(Window, windows, count, lt)
#undef lt
}
static id_type
add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
add_window(id_type os_window_id, id_type tab_id, PyObject *title, bool is_floating) {
WITH_TAB(os_window_id, tab_id);
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
make_os_window_context_current(osw);
zero_at_i(tab->windows, tab->num_windows);
initialize_window(tab->windows + tab->num_windows, title, true);
return tab->windows[tab->num_windows++].id;
initialize_window(tab->windows + tab->num_windows, title, true, is_floating);
Window *ans = tab->windows + tab->num_windows++;
sort_windows_in_render_order(tab->windows, tab->num_windows);
return ans->id;
END_WITH_TAB;
return 0;
}
@@ -1290,13 +1301,14 @@ static PyObject*
pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
Screen *screen;
PyObject *title = NULL;
if (!PyArg_ParseTuple(args, "O|U", &screen, &title)) return NULL;
int is_floating = 0;
if (!PyArg_ParseTuple(args, "O|Up", &screen, &title, &is_floating)) return NULL;
Window *w = PyMem_Calloc(sizeof(Window), 1);
if (!w) return NULL;
Py_INCREF(screen);
PyObject *ans = PyCapsule_New(w, "Window", destroy_mock_window);
if (ans != NULL) {
initialize_window(w, title, false);
initialize_window(w, title, false, is_floating);
w->render_data.screen = screen;
}
return ans;
@@ -1404,7 +1416,11 @@ THREE_ID(remove_window)
THREE_ID(detach_window)
THREE_ID(attach_window)
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
PYWRAP1(add_window) {
PyObject *title; id_type a, b; int is_floating;
PA("KKOp", &a, &b, &title, &is_floating);
return PyLong_FromUnsignedLongLong(add_window(a, b, title, is_floating));
}
PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
TWO_ID(remove_tab)
KI(set_active_tab)

View File

@@ -141,10 +141,15 @@ typedef struct WindowLogoRenderData {
bool using_default;
} WindowLogoRenderData;
typedef struct FloatRenderData {
bool is_floating;
} FloatRenderData;
typedef struct {
ssize_t vao_idx;
float xstart, ystart, dx, dy;
Screen *screen;
FloatRenderData float_data;
} WindowRenderData;
typedef struct {

View File

@@ -52,7 +52,7 @@ from .layout.base import Layout
from .layout.interface import create_layout_object_for, evict_cached_layouts
from .progress import ProgressState
from .tab_bar import TabBar, TabBarData
from .types import ac
from .types import FloatType, ac
from .typing_compat import EdgeLiteral, SessionTab, SessionType, TypedDict
from .utils import cmdline_for_hold, log_error, platform_window_id, resolved_shell, shlex_split, which
from .window import CwdRequest, Watchers, Window, WindowDict
@@ -192,7 +192,7 @@ class Tab: # {{{
def has_single_window_visible(self) -> bool:
if self.current_layout.only_active_window_visible:
return True
for i, g in enumerate(self.windows.iter_all_layoutable_groups(only_visible=True)):
for i, g in enumerate(self.windows.iter_all_layoutable_groups()):
if i > 0:
return False
return True
@@ -568,15 +568,21 @@ class Tab: # {{{
pass_fds: tuple[int, ...] = (),
remote_control_fd: int = -1,
next_to: Window | None = None,
float_type: FloatType = FloatType.none,
) -> Window:
child = self.launch_child(
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env,
is_clone_launch=is_clone_launch, add_listen_on_env_var=False if allow_remote_control and remote_control_passwords else True,
hold=hold, pass_fds=pass_fds, remote_control_fd=remote_control_fd,
)
floating_in = 0
if float_type is FloatType.window:
w = next_to or self.active_window
if w:
floating_in = w.id
window = Window(
self, child, self.args, override_title=override_title,
copy_colors_from=copy_colors_from, watchers=watchers,
self, child, self.args, override_title=override_title, floating_in_window=floating_in,
copy_colors_from=copy_colors_from, watchers=watchers, float_type=float_type,
allow_remote_control=allow_remote_control, remote_control_passwords=remote_control_passwords
)
# Must add child before laying out so that resize_pty succeeds
@@ -673,8 +679,8 @@ class Tab: # {{{
if self.windows:
if num < 0:
self.windows.make_previous_group_active(-num)
elif self.windows.num_groups:
self.current_layout.activate_nth_window(self.windows, min(num, self.windows.num_groups - 1))
elif self.windows.num_of_floating_groups_and_non_floating_groups:
self.current_layout.activate_nth_window(self.windows, min(num, self.windows.num_of_floating_groups_and_non_floating_groups - 1))
self.relayout_borders()
@ac('win', 'Focus the first window')
@@ -893,7 +899,7 @@ class Tab: # {{{
@property
def num_window_groups(self) -> int:
return self.windows.num_groups
return self.windows.num_of_non_floating_groups
def __contains__(self, window: Window) -> bool:
return window in self.windows

View File

@@ -27,6 +27,13 @@ class OverlayType(Enum):
main = 'main'
class FloatType(Enum):
none = 'none'
window = 'window'
tab = 'tab'
os_window = 'os_window'
class ParsedShortcut(NamedTuple):
mods: int
key_name: str

View File

@@ -91,7 +91,7 @@ from .keys import keyboard_mode_name, mod_mask
from .progress import Progress
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
from .types import FloatType, MouseEvent, OverlayType, WindowGeometry, ac, run_once
from .typing_compat import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import (
color_as_int,
@@ -623,12 +623,16 @@ class Window:
watchers: Watchers | None = None,
allow_remote_control: bool = False,
remote_control_passwords: dict[str, Sequence[str]] | None = None,
float_type: FloatType = FloatType.none,
floating_in_window: int = 0,
):
if watchers:
self.watchers = watchers
self.watchers.add(global_watchers())
else:
self.watchers = global_watchers().copy()
self.float_type = float_type
self.floating_in_window = floating_in_window
self.keys_redirected_till_ready_from: int = 0
self.last_focused_at = 0.
self.is_focused: bool = False
@@ -659,7 +663,7 @@ class Window:
self.child_title = self.default_title
self.title_stack: Deque[str] = deque(maxlen=10)
self.user_vars: dict[str, str] = {}
self.id: int = add_window(tab.os_window_id, tab.id, self.title)
self.id: int = add_window(tab.os_window_id, tab.id, self.title, self.is_floating)
self.clipboard_request_manager = ClipboardRequestManager(self.id)
self.margin = EdgeWidths()
self.padding = EdgeWidths()
@@ -682,6 +686,10 @@ class Window:
self.remote_control_passwords = remote_control_passwords
self.allow_remote_control = allow_remote_control
@property
def is_floating(self) -> bool:
return self.float_type is not FloatType.none
def remote_control_allowed(self, pcmd: dict[str, Any], extra_data: dict[str, Any]) -> bool:
if not self.allow_remote_control:
return False

View File

@@ -9,7 +9,7 @@ from itertools import count
from typing import Any, Deque, Union
from .fast_data_types import Color, get_options
from .types import OverlayType, WindowGeometry
from .types import FloatType, OverlayType, WindowGeometry
from .typing_compat import EdgeLiteral, TabType, WindowType
WindowOrId = Union[WindowType, int]
@@ -72,6 +72,13 @@ class WindowGroup:
else:
self.windows.append(window)
@property
def is_floating(self) -> bool:
for w in self.windows:
if w.is_floating:
return True
return False
def move_window_to_top_of_group(self, window: WindowType) -> bool:
try:
idx = self.windows.index(window)
@@ -100,22 +107,19 @@ class WindowGroup:
}
def decoration(self, which: EdgeLiteral, border_mult: int = 1, is_single_window: bool = False) -> int:
if not self.windows:
return 0
w = self.windows[0]
return w.effective_margin(which) + w.effective_border() * border_mult + w.effective_padding(which)
for w in self.windows:
return w.effective_margin(which) + w.effective_border() * border_mult + w.effective_padding(which)
return 0
def effective_padding(self, which: EdgeLiteral) -> int:
if not self.windows:
return 0
w = self.windows[0]
return w.effective_padding(which)
for w in self.windows:
return w.effective_padding(which)
return 0
def effective_border(self) -> int:
if not self.windows:
return 0
w = self.windows[0]
return w.effective_border()
for w in self.windows:
return w.effective_border()
return 0
def set_geometry(self, geom: WindowGeometry) -> None:
for w in self.windows:
@@ -123,22 +127,19 @@ class WindowGroup:
@property
def default_bg(self) -> Color:
if self.windows:
w = self.windows[-1]
for w in reversed(self.windows):
return w.screen.color_profile.default_bg or get_options().background
return get_options().background
@property
def geometry(self) -> WindowGeometry | None:
if self.windows:
w = self.windows[-1]
for w in reversed(self.windows):
return w.geometry
return None
@property
def is_visible_in_layout(self) -> bool:
if self.windows:
w = self.windows[-1]
for w in reversed(self.windows):
return w.is_visible_in_layout
return False
@@ -202,7 +203,7 @@ class WindowList:
def set_active_group_idx(self, i: int, notify: bool = True) -> bool:
changed = False
if i != self._active_group_idx and 0 <= i < len(self.groups):
if i != self._active_group_idx and 0 <= i < self.num_of_floating_groups_and_non_floating_groups:
old_active_window = self.active_window
g = self.active_group
if g is not None:
@@ -226,14 +227,42 @@ class WindowList:
def change_tab(self, tab: TabType) -> None:
self.tabref = weakref.ref(tab)
def iter_windows_with_visibility(self) -> Iterator[tuple[WindowType, bool]]:
def iter_windows_with_visibility(self) -> Iterator[tuple[WindowType, bool, bool]]:
for g in self.groups:
aw = g.active_window_id
is_floating = g.is_floating
for window in g:
yield window, window.id == aw
yield window, window.id == aw, is_floating
def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]:
return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups)
def iter_all_layoutable_groups(self) -> Iterator[WindowGroup]:
for g in self.groups:
if g.is_visible_in_layout and not g.is_floating:
yield g
def iter_all_floating_groups(self) -> Iterator[WindowGroup]:
for g in self.groups:
if g.is_floating:
yield g
@property
def num_layoutable_groups(self) -> int:
ans = 0
for g in self.groups:
if g.is_visible_in_layout and not g.is_floating:
ans += 1
return ans
@property
def num_of_non_floating_groups(self) -> int:
ans = 0
for g in self.groups:
if not g.is_floating:
ans += 1
return ans
@property
def num_of_floating_groups_and_non_floating_groups(self) -> int:
return len(self.groups)
def iter_windows_with_number(self, only_visible: bool = True) -> Iterator[tuple[int, WindowType]]:
for i, g in enumerate(self.groups):
@@ -259,10 +288,6 @@ class WindowList:
return
self.set_active_group_idx(len(self.groups) - 1, notify=notify)
@property
def num_groups(self) -> int:
return len(self.groups)
def group_for_window(self, x: WindowOrId) -> WindowGroup | None:
q = self.id_map[x] if isinstance(x, int) else x
for g in self.groups:
@@ -308,6 +333,31 @@ class WindowList:
return self.id_map[self.groups[self.active_group_idx].active_window_id]
return None
@property
def active_non_floating_window(self) -> WindowType | None:
w = self.active_window
if w is None:
return None
if not w.is_floating:
return w
if w.float_type is FloatType.window:
parent = self.id_map.get(w.floating_in_window)
if parent is not None:
g = self.group_for_window(parent)
if g is not None:
ans = self.id_map.get(g.active_window_id)
if ans is not None:
return ans
# tab or os window float or parent window closed
gid_map = {g.id: g for g in self.groups}
for gid in reversed(self.active_group_history):
g = gid_map.get(gid)
if g is not None:
w = self.id_map.get(g.active_window_id)
if w is not None and not w.is_floating:
return w
return None
@property
def active_group_main(self) -> WindowType | None:
with suppress(Exception):
@@ -343,22 +393,28 @@ class WindowList:
if group_of is not None:
target_group = self.group_for_window(group_of)
if target_group is None and next_to is not None:
q = self.id_map[next_to] if isinstance(next_to, int) else next_to
pos = -1
for i, g in enumerate(self.groups):
if q in g:
pos = i
break
if pos > -1:
if window.is_floating:
if target_group is None:
target_group = WindowGroup()
self.groups.insert(pos + (0 if before else 1), target_group)
if target_group is None:
target_group = WindowGroup()
if before:
self.groups.insert(0, target_group)
else:
self.groups.append(target_group)
else:
if target_group is None and next_to is not None:
q = self.id_map[next_to] if isinstance(next_to, int) else next_to
pos = -1
for i, g in enumerate(self.groups):
if q in g:
pos = i
break
if pos > -1:
target_group = WindowGroup()
self.groups.insert(pos + (0 if before else 1), target_group)
if target_group is None:
target_group = WindowGroup()
if before:
self.groups.insert(0, target_group)
else:
self.groups.append(target_group)
self.move_floating_groups_to_end()
old_active_window = self.active_window
target_group.add_window(window, head_of_group=head_of_group)
@@ -372,6 +428,14 @@ class WindowList:
self.notify_on_active_window_change(old_active_window, new_active_window)
return target_group
def move_floating_groups_to_end(self) -> None:
fg: list[WindowGroup] = []
ng: list[WindowGroup] = []
for g in self.groups:
(fg if g.is_floating else ng).append(g)
ng.extend(fg)
self.groups = ng
def remove_window(self, x: WindowOrId) -> None:
old_active_window = self.active_window
q = self.id_map[x] if isinstance(x, int) else x
@@ -398,8 +462,8 @@ class WindowList:
def active_window_in_nth_group(self, n: int, clamp: bool = False) -> WindowType | None:
if clamp:
n = max(0, min(n, self.num_groups - 1))
if 0 <= n < self.num_groups:
n = max(0, min(n, len(self.groups) - 1))
if 0 <= n < len(self.groups):
return self.id_map.get(self.groups[n].active_window_id)
return None
@@ -410,14 +474,14 @@ class WindowList:
return None
def activate_next_window_group(self, delta: int) -> None:
self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_groups, delta))
self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_of_floating_groups_and_non_floating_groups, delta))
def move_window_group(self, by: int | None = None, to_group: int | None = None) -> bool:
if self.active_group_idx < 0 or not self.groups:
return False
target = -1
if by is not None:
target = wrap_increment(self.active_group_idx, self.num_groups, by)
target = wrap_increment(self.active_group_idx, len(self.groups), by)
if to_group is not None:
for i, group in enumerate(self.groups):
if group.id == to_group:
@@ -427,13 +491,14 @@ class WindowList:
if target == self.active_group_idx:
return False
self.groups[self.active_group_idx], self.groups[target] = self.groups[target], self.groups[self.active_group_idx]
self.move_floating_groups_to_end()
self.set_active_group_idx(target)
return True
return False
def compute_needs_borders_map(self, draw_active_borders: bool) -> dict[int, bool]:
ag = self.active_group
return {gr.id: ((gr is ag and draw_active_borders) or gr.needs_attention) for gr in self.groups}
return {gr.id: ((gr is ag and draw_active_borders) or gr.needs_attention) for gr in self.groups if not gr.is_floating}
@property
def num_visble_groups(self) -> int:

View File

@@ -14,6 +14,7 @@ class Window:
def __init__(self, win_id, overlay_for=None, overlay_window_id=None):
self.id = win_id
self.is_floating = False
self.overlay_for = overlay_for
self.overlay_window_id = overlay_window_id
self.is_visible_in_layout = True
@@ -98,15 +99,15 @@ class TestLayout(BaseTest):
check_visible()
# Test nth_window
for i in range(windows.num_groups):
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
q.activate_nth_window(windows, i)
self.ae(windows.active_group_idx, i)
expect_ids(*range(1, len(windows)+1))
check_visible()
# Test next_window
for i in range(2 * windows.num_groups):
expected = (windows.active_group_idx + 1) % windows.num_groups
for i in range(2 * windows.num_of_floating_groups_and_non_floating_groups):
expected = (windows.active_group_idx + 1) % windows.num_of_floating_groups_and_non_floating_groups
q.next_window(windows)
self.ae(windows.active_group_idx, expected)
expect_ids(*range(1, len(windows)+1))
@@ -127,9 +128,9 @@ class TestLayout(BaseTest):
# Test add_window
windows.set_active_group_idx(4)
q.add_window(windows, Window(6))
self.ae(windows.num_groups, 6)
self.ae(windows.num_of_floating_groups_and_non_floating_groups, 6)
self.ae(windows.active_group_idx, 5)
expect_ids(*range(1, windows.num_groups+1))
expect_ids(*range(1, windows.num_of_floating_groups_and_non_floating_groups+1))
check_visible()
# Test remove_window
@@ -150,7 +151,7 @@ class TestLayout(BaseTest):
expect_ids(2, 3, 5, 6)
# Test set_active_window
for i in range(windows.num_groups):
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
windows.set_active_group_idx(i)
self.ae(i, windows.active_group_idx)
check_visible()
@@ -177,10 +178,10 @@ class TestLayout(BaseTest):
w = Window(len(windows) + 1)
windows.add_window(w)
expect_ids(1, 2, 3, 4, 5, 6)
self.ae(windows.active_group_idx, windows.num_groups - 1)
self.ae(windows.active_group_idx, windows.num_of_floating_groups_and_non_floating_groups - 1)
# Test nth_window
for i in range(windows.num_groups):
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
q.activate_nth_window(windows, i)
self.ae(windows.active_group_idx, i)
if i == overlaid_group:
@@ -189,8 +190,8 @@ class TestLayout(BaseTest):
check_visible()
# Test next_window
for i in range(windows.num_groups):
expected = (windows.active_group_idx + 1) % windows.num_groups
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
expected = (windows.active_group_idx + 1) % windows.num_of_floating_groups_and_non_floating_groups
q.next_window(windows)
self.ae(windows.active_group_idx, expected)
expect_ids(1, 2, 3, 4, 5, 6)
@@ -210,7 +211,7 @@ class TestLayout(BaseTest):
check_visible()
# Test set_active_window
for i in range(windows.num_groups):
for i in range(windows.num_of_floating_groups_and_non_floating_groups):
windows.set_active_group_idx(i)
self.ae(i, windows.active_group_idx)
if i == overlaid_group:

View File

@@ -15,6 +15,7 @@ in
xxHash
simde
go_1_23
matplotlib
]
++ optionals stdenv.isDarwin [
Cocoa
@@ -25,7 +26,6 @@ in
OpenGL
UniformTypeIdentifiers
libpng
python3
zlib
]
++ lib.optionals (stdenv.isDarwin && (builtins.hasAttr "UserNotifications" darwin.apple_sdk.frameworks)) [
@@ -46,6 +46,10 @@ in
wayland
openssl
dbus
cairo #
]
++ lib.optionals stdenv.hostPlatform.isLinux [
wayland-scanner
]
++ checkInputs;
@@ -77,10 +81,21 @@ in
if stdenv.isDarwin
then ''
export KITTY_NO_LTO=
# Add fonts by hand
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
fi
''
else ''
export KITTY_EGL_LIBRARY='${lib.getLib libGL}/lib/libEGL.so.1'
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
export KITTY_FONTCONFIG_LIBRARY='${fontconfig.lib}/lib/libfontconfig.so'
# Add fonts by hand
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
fi
'';
}