linuxOS_AP05/debian/test/usr/lib/python3/dist-packages/Onboard/TouchHandles.py

428 lines
13 KiB
Python
Raw Normal View History

2025-09-26 01:40:02 +00:00
# -*- coding: utf-8 -*-
# Copyright © 2012-2014, 2016-2017 marmuta <marmvta@gmail.com>
#
# This file is part of Onboard.
#
# Onboard is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Onboard is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Enlarged drag handles for resizing or moving """
from __future__ import division, print_function, unicode_literals
from math import pi, sqrt
import cairo
from Onboard.utils import Rect, drop_shadow
from Onboard.definitions import Handle
### Logging ###
import logging
_logger = logging.getLogger("TouchHandles")
###############
class TouchHandle(object):
""" Enlarged drag handle for resizing or moving """
id = None
prelight = False
pressed = False
corner_radius = 0 # radius of the outer corners (window edges)
_size = (40, 40)
_fallback_size = (40, 40)
_hit_proximity_factor = 1.5
_rect = None
_scale = 1.0 # scale of handle relative to resize handles
_handle_alpha = 0.45
_shadow_alpha = 0.04
_shadow_size = 8
_shadow_offset = (0.0, 2.0)
_screen_dpi = 96
_handle_angles = {} # dictionary at class scope!
lock_x_axis = False
lock_y_axis = False
def __init__(self, id):
self.id = id
# initialize angles
if not self._handle_angles:
for i, h in enumerate(Handle.EDGES):
self._handle_angles[h] = i * pi / 2.0
for i, h in enumerate(Handle.CORNERS):
self._handle_angles[h] = i * pi / 2.0 + pi / 4.0
self._handle_angles[Handle.MOVE] = 0.0
def get_rect(self):
rect = self._rect
if not rect is None and \
self.pressed:
rect = rect.offset(1.0, 1.0)
return rect
def get_radius(self):
w, h = self.get_rect().get_size()
return min(w, h) / 2.0
def get_shadow_rect(self):
rect = self.get_rect()
if rect:
rect = rect.inflate(self._shadow_size+1)
rect.w += self._shadow_offset[0]
rect.h += self._shadow_offset[1]
return rect
def get_arrow_angle(self):
return self._handle_angles[self.id]
def is_edge_handle(self):
return self.id in Handle.EDGES
def is_corner_handle(self):
return self.id in Handle.CORNERS
def update_position(self, canvas_rect):
w, h = self._size
w = min(w, canvas_rect.w / 3.0)
w = min(w, canvas_rect.h / 3.0)
h = w
self._scale = 1.0
xc, yc = canvas_rect.get_center()
if self.id is Handle.MOVE:
d = min(canvas_rect.w - 2.0 * w, canvas_rect.h - 2.0 * h)
self._scale = 1.4
w = min(w * self._scale, d)
h = min(h * self._scale, d)
if self.id in [Handle.WEST,
Handle.NORTH_WEST,
Handle.SOUTH_WEST]:
x = canvas_rect.left()
if self.id in [Handle.NORTH,
Handle.NORTH_WEST,
Handle.NORTH_EAST]:
y = canvas_rect.top()
if self.id in [Handle.EAST,
Handle.NORTH_EAST,
Handle.SOUTH_EAST]:
x = canvas_rect.right() - w
if self.id in [Handle.SOUTH,
Handle.SOUTH_WEST,
Handle.SOUTH_EAST]:
y = canvas_rect.bottom() - h
if self.id in [Handle.MOVE, Handle.EAST, Handle.WEST]:
y = yc - h / 2.0
if self.id in [Handle.MOVE, Handle.NORTH, Handle.SOUTH]:
x = xc - w / 2.0
self._rect = Rect(x, y, w, h)
def hit_test(self, point):
rect = self.get_rect().grow(self._hit_proximity_factor)
radius = self.get_radius() * self._hit_proximity_factor
if rect and rect.is_point_within(point):
_win = self._window.get_window()
if _win:
context = _win.cairo_create()
self._build_handle_path(context, rect, radius)
return context.in_fill(*point)
return False
xc, yc = rect.get_center()
dx = xc - point[0]
dy = yc - point[1]
d = sqrt(dx*dx + dy*dy)
return d <= radius
def draw(self, context):
if self.pressed:
alpha_factor = 1.5
else:
alpha_factor = 1.0
context.new_path()
self._draw_handle_shadow(context, alpha_factor)
self._draw_handle(context, alpha_factor)
self._draw_arrows(context)
def _draw_handle(self, context, alpha_factor):
radius = self.get_radius()
line_width = radius / 15.0
alpha = self._handle_alpha * alpha_factor
if self.pressed:
context.set_source_rgba(0.78, 0.33, 0.17, alpha)
elif self.prelight:
context.set_source_rgba(0.98, 0.53, 0.37, alpha)
else:
context.set_source_rgba(0.78, 0.33, 0.17, alpha)
self._build_handle_path(context)
context.fill_preserve()
context.set_line_width(line_width)
context.stroke()
def _draw_handle_shadow(self, context, alpha_factor):
rect = self.get_rect()
context.save()
# There is a massive performance boost for groups when clipping is used.
# Integer limits are again dramatically faster (x4) then using floats.
# for 1000x draw_drop_shadow:
# with clipping: ~300ms, without: ~11000ms
context.rectangle(*self.get_shadow_rect().int())
context.clip()
context.push_group()
# draw the shadow
context.push_group_with_content(cairo.CONTENT_ALPHA)
self._build_handle_path(context)
context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
context.fill()
group = context.pop_group()
drop_shadow(context, group, rect,
self._shadow_size,
self._shadow_offset,
self._shadow_alpha,
5)
# cut out the handle area, because the handle is transparent
context.save()
context.set_operator(cairo.OPERATOR_CLEAR)
context.set_source_rgba(0.0, 0.0, 0.0, 1.0)
self._build_handle_path(context)
context.fill()
context.restore()
context.pop_group_to_source()
context.paint()
context.restore()
def _draw_arrows(self, context):
radius = self.get_radius()
xc, yc = self.get_rect().get_center()
scale = radius / 2.0 / self._scale * 1.2
angle = self.get_arrow_angle()
if self.id == Handle.MOVE:
num_arrows = 4
if self.lock_x_axis:
num_arrows -= 2
angle += pi * 0.5
if self.lock_y_axis:
num_arrows -= 2
else:
num_arrows = 2
angle_step = 2.0 * pi / num_arrows
for i in range(num_arrows):
context.save()
context.translate(xc, yc)
context.rotate(angle + i * angle_step)
context.scale(scale, scale)
# arrow distance from center
if self.id is Handle.MOVE:
context.translate(0.9, 0)
else:
context.translate(0.30, 0)
self._draw_arrow(context)
context.restore()
def _draw_arrow(self, context):
context.move_to( 0.0, -0.5)
context.line_to( 0.5, 0.0)
context.line_to( 0.0, 0.5)
context.close_path()
context.set_source_rgba(1.0, 1.0, 1.0, 0.8)
context.fill_preserve()
context.set_source_rgba(0.0, 0.0, 0.0, 0.8)
context.set_line_width(0)
context.stroke()
def _build_handle_path(self, context, rect = None, radius = None):
if rect is None:
rect = self.get_rect()
if radius is None:
radius = self.get_radius()
xc, yc = rect.get_center()
corner_radius = self.corner_radius
angle = self.get_arrow_angle()
m = cairo.Matrix()
m.translate(xc, yc)
m.rotate(angle)
if self.is_edge_handle():
p0 = m.transform_point(radius, -radius)
p1 = m.transform_point(radius, radius)
context.arc(xc, yc, radius, angle + pi / 2.0, angle + pi / 2.0 + pi)
context.line_to(*p0)
context.line_to(*p1)
context.close_path()
elif self.is_corner_handle():
m.rotate(-pi / 4.0) # rotate to SOUTH_EAST
context.arc(xc, yc, radius, angle + 3 * pi / 4.0,
angle + 5 * pi / 4.0)
pt = m.transform_point(radius, -radius)
context.line_to(*pt)
if corner_radius:
# outer corner, following the rounded window corner
pt = m.transform_point(radius, radius - corner_radius)
ptc = m.transform_point(radius - corner_radius,
radius - corner_radius)
context.line_to(*pt)
context.arc(ptc[0], ptc[1], corner_radius,
angle - pi / 4.0, angle + pi / 4.0)
else:
pt = m.transform_point(radius, radius)
context.line_to(*pt)
pt = m.transform_point(-radius, radius)
context.line_to(*pt)
context.close_path()
else:
context.arc(xc, yc, radius, 0, 2.0 * pi)
def redraw(self):
rect = self.get_shadow_rect()
if rect:
self._window.queue_draw_area(*rect)
class TouchHandles(object):
""" Full set of resize and move handles """
active = False
opacity = 1.0
rect = None
def __init__(self):
self.handles = []
self._handle_pool = [TouchHandle(Handle.MOVE),
TouchHandle(Handle.NORTH_WEST),
TouchHandle(Handle.NORTH),
TouchHandle(Handle.NORTH_EAST),
TouchHandle(Handle.EAST),
TouchHandle(Handle.SOUTH_EAST),
TouchHandle(Handle.SOUTH),
TouchHandle(Handle.SOUTH_WEST),
TouchHandle(Handle.WEST)]
def set_active_handles(self, handle_ids):
self.handles = []
for handle in self._handle_pool:
if handle.id in handle_ids:
self.handles.append(handle)
def set_window(self, window):
for handle in self._handle_pool:
handle._window = window
def update_positions(self, canvas_rect):
self.rect = canvas_rect
for handle in self.handles:
handle.update_position(canvas_rect)
def draw(self, context):
if self.opacity:
clip_rect = Rect.from_extents(*context.clip_extents())
for handle in self.handles:
rect = handle.get_shadow_rect()
if rect.intersects(clip_rect):
context.save()
context.rectangle(*rect.int())
context.clip()
context.push_group()
handle.draw(context)
context.pop_group_to_source()
context.paint_with_alpha(self.opacity);
context.restore()
def redraw(self):
if self.rect:
for handle in self.handles:
handle.redraw()
def hit_test(self, point):
if self.active:
for handle in self.handles:
if handle.hit_test(point):
return handle.id
def set_prelight(self, handle_id):
for handle in self.handles:
prelight = handle.id == handle_id and not handle.pressed
if handle.prelight != prelight:
handle.prelight = prelight
handle.redraw()
def set_pressed(self, handle_id):
for handle in self.handles:
pressed = handle.id == handle_id
if handle.pressed != pressed:
handle.pressed = pressed
handle.redraw()
def set_corner_radius(self, corner_radius):
for handle in self.handles:
handle.corner_radius = corner_radius
def set_monitor_dimensions(self, size_px, size_mm):
min_monitor_mm = 50
target_size_mm = (5, 5)
min_size = TouchHandle._fallback_size
if size_mm[0] < min_monitor_mm or \
size_mm[1] < min_monitor_mm:
w = 0
h = 0
else:
w = size_px[0] / size_mm[0] * target_size_mm[0]
h = size_px[0] / size_mm[0] * target_size_mm[0]
size = max(w, min_size[0]), max(h, min_size[1])
TouchHandle._size = size
def lock_x_axis(self, lock):
""" Set to False to constraint movement in x. """
for handle in self.handles:
handle.lock_x_axis = lock
def lock_y_axis(self, lock):
""" Set to True to constraint movement in y. """
for handle in self.handles:
handle.lock_y_axis = lock