diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 1c8307c5b..644219cae 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -328,9 +328,7 @@ opt('cursor_underline_thickness', '2.0', long_text='The thickness of the underline cursor (in pts).' ) -opt('cursor_blink_interval', '-1', - option_type='float', ctype='time', - long_text=''' +opt('cursor_blink_interval', '-1', option_type='cursor_blink_interval', ctype='!cursor_blink_interval', long_text=''' The interval to blink the cursor (in seconds). Set to zero to disable blinking. Negative values mean use system default. Note that the minimum interval will be limited to :opt:`repaint_delay`. diff --git a/kitty/options/parse.py b/kitty/options/parse.py index f45d24a4c..6c6c4bbfe 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -9,17 +9,17 @@ from kitty.conf.utils import ( from kitty.options.utils import ( action_alias, active_tab_title_template, allow_hyperlinks, bell_on_tab, box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, clone_source_strategies, - config_or_absolute_path, copy_on_select, cursor_text_color, deprecated_adjust_line_height, - deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, - deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations, - macos_option_as_alt, macos_titlebar_color, menu_map, modify_font, narrow_symbols, - notify_on_cmd_finish, optional_edge_width, parse_font_spec, parse_map, parse_mouse_map, - paste_actions, remote_control_password, resize_debounce_time, scrollback_lines, - scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol, - tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, - tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size, - to_layout_names, to_modifiers, url_prefixes, url_style, visual_window_select_characters, - window_border_width, window_logo_scale, window_size + config_or_absolute_path, copy_on_select, cursor_blink_interval, cursor_text_color, + deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases, + deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, + edge_width, env, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, + menu_map, modify_font, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec, + parse_map, parse_mouse_map, paste_actions, remote_control_password, resize_debounce_time, + scrollback_lines, scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, + tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, + tab_font_style, tab_separator, tab_title_template, titlebar_color, to_cursor_shape, + to_cursor_unfocused_shape, to_font_size, to_layout_names, to_modifiers, url_prefixes, url_style, + visual_window_select_characters, window_border_width, window_logo_scale, window_size ) @@ -915,7 +915,7 @@ class Parser: ans['cursor_beam_thickness'] = positive_float(val) def cursor_blink_interval(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['cursor_blink_interval'] = float(val) + ans['cursor_blink_interval'] = cursor_blink_interval(val) def cursor_shape(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['cursor_shape'] = to_cursor_shape(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 0cc1f640d..308bb44da 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -137,7 +137,7 @@ convert_from_opts_cursor_underline_thickness(PyObject *py_opts, Options *opts) { static void convert_from_python_cursor_blink_interval(PyObject *val, Options *opts) { - opts->cursor_blink_interval = parse_s_double_to_monotonic_t(val); + cursor_blink_interval(val, opts); } static void diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 4ee81ed04..ab9d215c9 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -123,6 +123,47 @@ window_logo_path(PyObject *src, Options *opts) { STR_SETTER(default_window_logo) #undef STR_SETTER +static void +add_easing_function(Animation *a, PyObject *e, double y_at_start, double y_at_end) { +#define G(name) RAII_PyObject(name, PyObject_GetAttrString(e, #name)) +#define D(container, idx) PyFloat_AsDouble(PyTuple_GET_ITEM(container, idx)) + G(type); + if (PyUnicode_CompareWithASCIIString(type, "cubic-bezier")) { + G(cubic_bezier_points); + add_cubic_bezier_animation(a, y_at_start, y_at_end, D(cubic_bezier_points, 0), D(cubic_bezier_points, 1), D(cubic_bezier_points, 2), D(cubic_bezier_points, 3)); + } else if (PyUnicode_CompareWithASCIIString(type, "linear")) { + G(linear_count); G(linear_params); G(linear_positions); + size_t count = PyLong_AsSize_t(linear_count); + RAII_ALLOC(double, params, malloc(2 * sizeof(double) * count)); + if (params) { + double *positions = params + count; + for (size_t i = 0; i < count; i++) { + params[i] = D(linear_params, i); positions[i] = D(linear_positions, i); + } + add_linear_animation(a, y_at_start, y_at_end, count, params, positions); + } + } else if (PyUnicode_CompareWithASCIIString(type, "steps")) { + G(num_steps); G(jump_type); + add_steps_animation(a, y_at_start, y_at_end, PyLong_AsSize_t(num_steps), PyLong_AsLong(jump_type)); + } +#undef D +#undef G +} + +static inline void +cursor_blink_interval(PyObject *src, Options *opts) { + free_animation(opts->animation.cursor); + opts->cursor_blink_interval = parse_s_double_to_monotonic_t(PyTuple_GET_ITEM(src, 0)); + if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 1))) { + add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 1), 1, 0); + if (PyObject_IsTrue(PyTuple_GET_ITEM(src, 2))) { + add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 2), 0, 1); + } else { + add_easing_function(opts->animation.cursor, PyTuple_GET_ITEM(src, 1), 0, 1); + } + } +} + static void parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) { PyObject *mv = PyObject_GetAttrString(val, "mod_value"); diff --git a/kitty/options/types.py b/kitty/options/types.py index 20b8ac9f3..03718792e 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -503,7 +503,7 @@ class Options: copy_on_select: str = '' cursor: typing.Optional[kitty.fast_data_types.Color] = Color(204, 204, 204) cursor_beam_thickness: float = 1.5 - cursor_blink_interval: float = -1.0 + cursor_blink_interval: typing.Tuple[float, kitty.options.utils.EasingFunction, kitty.options.utils.EasingFunction] = (-1.0, kitty.options.utils.EasingFunction(), kitty.options.utils.EasingFunction()) cursor_shape: int = 1 cursor_shape_unfocused: int = 0 cursor_stop_blinking_after: float = 15.0 diff --git a/kitty/options/utils.py b/kitty/options/utils.py index f7de683f9..3bd180b53 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -1392,6 +1392,34 @@ def parse_font_spec(spec: str) -> FontSpec: return FontSpec.from_setting(spec) +class EasingFunction(NamedTuple): + type: str = '' + + num_steps: int = 0 + jump_type: int = 0 + + linear_count: int = 0 + linear_params: Tuple[float, ...] = () + linear_positions: Tuple[float, ...] = () + + cubic_bezier_points: Tuple[float, ...] = () + + def __repr__(self) -> str: + fields = ', '.join(f'{f}={getattr(self, f)!r}' for f in self._fields if getattr(self, f) != self._field_defaults[f]) + return f'kitty.options.utils.EasingFunction({fields})' + + def __bool__(self) -> bool: + return bool(self.type) + + +def cursor_blink_interval(spec: str) -> Tuple[float, EasingFunction, EasingFunction]: + try: + interval = float(spec) + return interval, EasingFunction(), EasingFunction() + except Exception: + return -1, EasingFunction(), EasingFunction() + + def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None: if not hasattr(deprecated_hide_window_decorations_aliases, key): setattr(deprecated_hide_window_decorations_aliases, key, True)