def _update_on_boundaries_change(self, *args): zoom_pos = Vector(self.zoom_pos) pos = self._old_zoom_pos_limit[0] top_right = self._old_zoom_pos_limit[1] - Vector(self.size) hint = (zoom_pos - pos).safe_div(top_right - pos) self._old_zoom_pos_limit = self._get_zoom_pos_limit() self.set_size(Vector(self.size)) self._set_zoom_pos_from_hint(hint) self.set_pos(Vector(self.zoom_pos))
def _get_zoom_pos_limit(self): tz_size = self.uvs_size * self.texture_size origin = tz_size * 0.5 offset = -origin offset *= 1.0 / self.zoom offset += origin offset = offset.safe_div(tz_size) * Vector(self.size) scaled_pos = Vector(self.pos_limit[:2]) - offset scaled_top_left = scaled_pos + self.container_size + 2 * offset return [scaled_pos, scaled_top_left]
def on_touch_move(self, touch): if touch.grab_current is self: if touch.ud[id(self)].get('zooming', False): return True elif touch.ud[id(self)].get('resizing', False): return super(ZoomBox, self).on_touch_move(touch) else: zoom_pos = Vector(self.zoom_pos) + Vector(touch.dx, touch.dy) self._set_zoom_pos(zoom_pos) self.set_pos(Vector(self.zoom_pos)) return True return super(ZoomBox, self).on_touch_move(touch)
def _update_uvs_model(self): uvs_size = self.uvs_size uvs_origin = uvs_size * 0.5 uvs = [ 0.0, uvs_size[1], uvs_size[0], uvs_size[1], uvs_size[0], 0.0, 0.0, 0.0 ] for i in range(0, 8, 2): temp = Vector(uvs[i], uvs[i + 1]) temp -= uvs_origin temp *= 1.0 / self.zoom temp = temp.rotate(self.angle) temp += uvs_origin uvs[i] = temp.x uvs[i + 1] = temp.y self._uvs = uvs
def _compute_uvs_pos(self): cont_size = self.container_size size = Vector(self.size) tex_size = self.texture_size zoom_local_pos = Vector(self.zoom_pos) - Vector(self.pos_limit[:2]) allowed_size = cont_size - size dp_pos = zoom_local_pos.safe_div(allowed_size) dp_pos.y = 1.0 - dp_pos.y if allowed_size.y != 0 else 0.0 tex_zoom_size = tex_size.safe_div(cont_size) * size allowed_uvs_size = (tex_size - tex_zoom_size).safe_div(tex_size) pos = dp_pos * allowed_uvs_size if self.angle % 360 != 0: uvs_origin = allowed_uvs_size * 0.5 pos -= uvs_origin pos = pos.rotate(self.angle) pos += uvs_origin return pos
def set_size_and_pos(self, new_size, new_pos): self.set_size(new_size) if self._resizing: self.set_pos(new_pos) self._set_zoom_pos(new_pos) else: # Dragging the zoom box self.set_pos(Vector(self.zoom_pos))
def __init__(self, **kwargs): pos_limit = [0.0, 0.0, self.default_size[0], self.default_size[1]] kwargs.setdefault('pos_limit', pos_limit) self._resizing = False self._uvs = None self._flag_uvs_model = True self._old_zoom_pos_limit = [ Vector(pos_limit[:2]), Vector(pos_limit[2:]) ] fbind = self.fbind fbind('center', self._trigger_update_callback) fbind('zoom_pos', self._trigger_update_callback) fbind('container_size', self._set_flag_uvs) fbind('size', self._set_flag_uvs) fbind('texture', self._set_flag_uvs) fbind('zoom', self._set_flag_uvs) fbind('angle', self._set_flag_uvs) fbind('disabled', self._set_flag_uvs) super(ZoomBox, self).__init__(**kwargs) self._old_zoom_pos_limit = self._get_zoom_pos_limit()
def on_touch_down(self, touch): if self.disabled and self.collide_point(*touch.pos): return False if self.collide_point(*touch.pos): heading = 0.0 if touch.button == 'scrollup': heading = -1.0 elif touch.button == 'scrolldown': heading = 1.0 if heading: touch.ud[id(self)] = {'zooming': True} touch.grab(self) self.zoom = min(self.zoom_max, max(1.0, self.zoom + heading * self.zoom_step)) self._set_zoom_pos(Vector(self.zoom_pos)) return True value = super(ZoomBox, self).on_touch_down(touch) if value and touch.ud[id(self)].get('resizing', False): self._resizing = True return value
def on_touch_move(self, touch): if touch.grab_current is not self: return False uid = id(self) if touch.ud[uid]['resizing']: origin = touch.ud[uid]['origin'] heading = touch.ud[uid]['heading'] delta = Vector(touch.pos) - origin pos = heading * (origin + delta) pos += (Vector.ones() - heading) * (origin + Vector(self.size)) self.set_size_and_pos(abs(pos - origin), Vector.min(pos, origin)) self.dispatch('on_resize') return True else: self.set_pos(Vector(self.pos) + Vector(touch.dx, touch.dy)) self.dispatch('on_drag') return True
class CropBox(Widget): border_color = ListProperty([1, 0, 0, 1.0]) border_width = NumericProperty('1dp') border_collide_width_min = NumericProperty('10dp') pos_limit = ListProperty( [-float('inf'), -float('inf'), float('inf'), float('inf')]) size_min = ListProperty([dp(10), dp(10)]) default_size = ListProperty([dp(120), dp(120)]) default_pos_hint = ListProperty([0.5, 0.5]) border_width_modifier = AliasProperty(lambda self: 2.0 if self.border_width > 1.0 else 1.0, None, bind=['border_width'], cache=True) displayed_border_width = AliasProperty( lambda self: self.border_width * self.border_width_modifier, None, bind=['border_width', 'border_width_modifier'], cache=True) def _get_current_collide_border_width(self): return max(self.displayed_border_width, self.border_collide_width_min) current_collide_border_width = AliasProperty( _get_current_collide_border_width, None, bind=['displayed_border_width', 'border_collide_width_min'], cache=True) container_size = AliasProperty( lambda self: Vector(self.pos_limit[2:]) - Vector(self.pos_limit[:2]), None, bind=['pos_limit'], cache=True) __events__ = ('on_drag_start', 'on_drag', 'on_drag_stop', 'on_resize_start', 'on_resize', 'on_resize_stop') def __init__(self, **kwargs): kwargs.setdefault('size_hint', (None, None)) self._first_init = True self.trigger_update = trigger = Clock.create_trigger(self._update, -1) self.fbind('pos_limit', self._update_on_boundaries_change) super(CropBox, self).__init__(**kwargs) trigger() def _update(self, *args): if self._first_init: self.set_defaults() self._first_init = False def _update_on_boundaries_change(self, *args): self.set_size_and_pos(Vector(self.size), Vector(self.pos)) def set_defaults(self): self.set_size(Vector(self.default_size)) hint = Vector(self.default_pos_hint) pos = (self.container_size - Vector(self.size)) * hint pos += Vector(self.pos_limit[:2]) self.set_pos(pos) def set_size_and_pos(self, new_size, new_pos): self.set_size(new_size) self.set_pos(new_pos) def set_size(self, new_size): self.size[:] = Vector.clamp( new_size, Vector(self.size_min), Vector(self.pos_limit[2:]) - Vector(self.pos_limit[:2])) def set_pos(self, new_pos): self.pos[:] = Vector.clamp( new_pos, Vector(self.pos_limit[:2]), Vector(self.pos_limit[2:]) - Vector(self.size)) def collide_with_inner_box(self, x, y): width = Vector.ones() * self.current_collide_border_width pos = Vector(self.pos) return Vector.in_bbox((x, y), pos + width, pos + Vector(self.size) - width) def on_touch_down(self, touch): if self.disabled and self.collide_point(*touch.pos): return True data = {'resizing': False} in_outer = self.collide_point(*touch.pos) if in_outer and not self.collide_with_inner_box(*touch.pos): origin, heading = self._find_origin_point_and_heading(touch) data['resizing'] = True data['origin'] = origin data['heading'] = heading touch.ud[id(self)] = data touch.grab(self) self.dispatch('on_resize_start') return True if in_outer: touch.ud[id(self)] = data touch.grab(self) self.dispatch('on_drag_start') return True return False def on_touch_move(self, touch): if touch.grab_current is not self: return False uid = id(self) if touch.ud[uid]['resizing']: origin = touch.ud[uid]['origin'] heading = touch.ud[uid]['heading'] delta = Vector(touch.pos) - origin pos = heading * (origin + delta) pos += (Vector.ones() - heading) * (origin + Vector(self.size)) self.set_size_and_pos(abs(pos - origin), Vector.min(pos, origin)) self.dispatch('on_resize') return True else: self.set_pos(Vector(self.pos) + Vector(touch.dx, touch.dy)) self.dispatch('on_drag') return True def on_touch_up(self, touch): if touch.grab_current is self: touch.ungrab(self) if touch.ud[id(self)].get('resizing', False): self.dispatch('on_resize_stop') else: self.dispatch('on_drag_stop') return True def _find_origin_point_and_heading(self, touch): pos = Vector(self.pos) size = Vector(self.size) width = self.current_collide_border_width top_right = pos + size if pos.x <= touch.x <= pos.x + width: if pos.y <= touch.y <= pos.y + width: return pos + size, Vector.ones() elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos.x + size.x, pos.y), Vector.ones() if top_right.x - width <= touch.x <= top_right.x: if pos.y <= touch.y <= pos.y + width: return Vector(pos.x, pos.y + size.y), Vector.ones() elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos), Vector.ones() if pos.x + width <= touch.x <= top_right.x - width: if pos.y <= touch.y <= pos.y + width: return Vector(pos.x, pos.y + size.y), Vector(0.0, 1.0) elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos), Vector(0.0, 1.0) if pos.y + width <= touch.y <= top_right.y - width: if pos.x <= touch.x <= pos.x + width: return Vector(pos.x + size.x, pos.y), Vector(1.0, 0.0) elif top_right.x - width <= touch.x <= top_right.x: return Vector(pos), Vector(1.0, 0.0) def on_drag_start(self): pass def on_drag(self): pass def on_drag_stop(self): pass def on_resize_start(self): pass def on_resize(self): pass def on_resize_stop(self): pass
def set_pos(self, new_pos): self.pos[:] = Vector.clamp( new_pos, Vector(self.pos_limit[:2]), Vector(self.pos_limit[2:]) - Vector(self.size))
def set_defaults(self): self.set_size(Vector(self.default_size)) hint = Vector(self.default_pos_hint) pos = (self.container_size - Vector(self.size)) * hint pos += Vector(self.pos_limit[:2]) self.set_pos(pos)
class ZoomBox(CropBox): angle = NumericProperty(0.0) zoom = NumericProperty(2.0) zoom_max = NumericProperty(5.0) zoom_step = NumericProperty(0.5) zoom_pos = ListProperty([0.0, 0.0]) texture = ObjectProperty(None, allownone=True) texture_size = AliasProperty(lambda self: Vector(self.texture.size) if self.texture else Vector(), None, bind=['texture'], cache=True) def _get_uvs_size(self): tex_size = self.texture_size tz_size = tex_size.safe_div(self.container_size) * Vector(self.size) return tz_size.safe_div(tex_size) uvs_size = AliasProperty(_get_uvs_size, None, bind=['container_size', 'texture_size', 'size'], cache=True) _vertices = ListProperty( create_mesh_vertices((0, 0), (100, 100), DEFAULT_UVS)) def __init__(self, **kwargs): pos_limit = [0.0, 0.0, self.default_size[0], self.default_size[1]] kwargs.setdefault('pos_limit', pos_limit) self._resizing = False self._uvs = None self._flag_uvs_model = True self._old_zoom_pos_limit = [ Vector(pos_limit[:2]), Vector(pos_limit[2:]) ] fbind = self.fbind fbind('center', self._trigger_update_callback) fbind('zoom_pos', self._trigger_update_callback) fbind('container_size', self._set_flag_uvs) fbind('size', self._set_flag_uvs) fbind('texture', self._set_flag_uvs) fbind('zoom', self._set_flag_uvs) fbind('angle', self._set_flag_uvs) fbind('disabled', self._set_flag_uvs) super(ZoomBox, self).__init__(**kwargs) self._old_zoom_pos_limit = self._get_zoom_pos_limit() def set_defaults(self): self.set_size(Vector(self.default_size)) self._set_zoom_pos_from_hint(Vector(self.default_pos_hint)) self.set_pos(Vector(self.zoom_pos)) def on_touch_down(self, touch): if self.disabled and self.collide_point(*touch.pos): return False if self.collide_point(*touch.pos): heading = 0.0 if touch.button == 'scrollup': heading = -1.0 elif touch.button == 'scrolldown': heading = 1.0 if heading: touch.ud[id(self)] = {'zooming': True} touch.grab(self) self.zoom = min(self.zoom_max, max(1.0, self.zoom + heading * self.zoom_step)) self._set_zoom_pos(Vector(self.zoom_pos)) return True value = super(ZoomBox, self).on_touch_down(touch) if value and touch.ud[id(self)].get('resizing', False): self._resizing = True return value def on_touch_move(self, touch): if touch.grab_current is self: if touch.ud[id(self)].get('zooming', False): return True elif touch.ud[id(self)].get('resizing', False): return super(ZoomBox, self).on_touch_move(touch) else: zoom_pos = Vector(self.zoom_pos) + Vector(touch.dx, touch.dy) self._set_zoom_pos(zoom_pos) self.set_pos(Vector(self.zoom_pos)) return True return super(ZoomBox, self).on_touch_move(touch) def on_touch_up(self, touch): if touch.grab_current is self \ and touch.ud[id(self)].get('resizing', False): self._resizing = False return super(ZoomBox, self).on_touch_up(touch) def _trigger_update_callback(self, *args): self.trigger_update() def _update(self, *args): super(ZoomBox, self)._update(*args) self._update_vertices() def _set_flag_uvs(self, *args): self._flag_uvs_model = True self.trigger_update() def _set_zoom_pos_from_hint(self, hint): zoom_pos_limit = self._get_zoom_pos_limit() pos = zoom_pos_limit[0] top_right = zoom_pos_limit[1] - Vector(self.size) zoom_pos = (top_right - pos) * hint + pos self._set_zoom_pos(zoom_pos) def _update_on_boundaries_change(self, *args): zoom_pos = Vector(self.zoom_pos) pos = self._old_zoom_pos_limit[0] top_right = self._old_zoom_pos_limit[1] - Vector(self.size) hint = (zoom_pos - pos).safe_div(top_right - pos) self._old_zoom_pos_limit = self._get_zoom_pos_limit() self.set_size(Vector(self.size)) self._set_zoom_pos_from_hint(hint) self.set_pos(Vector(self.zoom_pos)) def set_size_and_pos(self, new_size, new_pos): self.set_size(new_size) if self._resizing: self.set_pos(new_pos) self._set_zoom_pos(new_pos) else: # Dragging the zoom box self.set_pos(Vector(self.zoom_pos)) def _set_zoom_pos(self, new_zoom_pos): zoom_pos_limit = self._get_zoom_pos_limit() self.zoom_pos[:] = Vector.clamp(new_zoom_pos, zoom_pos_limit[0], zoom_pos_limit[1] - Vector(self.size)) def _get_zoom_pos_limit(self): tz_size = self.uvs_size * self.texture_size origin = tz_size * 0.5 offset = -origin offset *= 1.0 / self.zoom offset += origin offset = offset.safe_div(tz_size) * Vector(self.size) scaled_pos = Vector(self.pos_limit[:2]) - offset scaled_top_left = scaled_pos + self.container_size + 2 * offset return [scaled_pos, scaled_top_left] def _update_vertices(self, *args): if self.disabled: return uvs_pos = self._compute_uvs_pos() if self._flag_uvs_model: self._update_uvs_model() self._flag_uvs_model = False uvs = self._uvs[:] for i in range(0, 8, 2): uvs[i] = uvs[i] + uvs_pos.x uvs[i + 1] = uvs[i + 1] + uvs_pos.y self._vertices[:] = create_mesh_vertices(self.pos, self.size, uvs) def _update_uvs_model(self): uvs_size = self.uvs_size uvs_origin = uvs_size * 0.5 uvs = [ 0.0, uvs_size[1], uvs_size[0], uvs_size[1], uvs_size[0], 0.0, 0.0, 0.0 ] for i in range(0, 8, 2): temp = Vector(uvs[i], uvs[i + 1]) temp -= uvs_origin temp *= 1.0 / self.zoom temp = temp.rotate(self.angle) temp += uvs_origin uvs[i] = temp.x uvs[i + 1] = temp.y self._uvs = uvs def _compute_uvs_pos(self): cont_size = self.container_size size = Vector(self.size) tex_size = self.texture_size zoom_local_pos = Vector(self.zoom_pos) - Vector(self.pos_limit[:2]) allowed_size = cont_size - size dp_pos = zoom_local_pos.safe_div(allowed_size) dp_pos.y = 1.0 - dp_pos.y if allowed_size.y != 0 else 0.0 tex_zoom_size = tex_size.safe_div(cont_size) * size allowed_uvs_size = (tex_size - tex_zoom_size).safe_div(tex_size) pos = dp_pos * allowed_uvs_size if self.angle % 360 != 0: uvs_origin = allowed_uvs_size * 0.5 pos -= uvs_origin pos = pos.rotate(self.angle) pos += uvs_origin return pos
def _set_zoom_pos(self, new_zoom_pos): zoom_pos_limit = self._get_zoom_pos_limit() self.zoom_pos[:] = Vector.clamp(new_zoom_pos, zoom_pos_limit[0], zoom_pos_limit[1] - Vector(self.size))
def _find_origin_point_and_heading(self, touch): pos = Vector(self.pos) size = Vector(self.size) width = self.current_collide_border_width top_right = pos + size if pos.x <= touch.x <= pos.x + width: if pos.y <= touch.y <= pos.y + width: return pos + size, Vector.ones() elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos.x + size.x, pos.y), Vector.ones() if top_right.x - width <= touch.x <= top_right.x: if pos.y <= touch.y <= pos.y + width: return Vector(pos.x, pos.y + size.y), Vector.ones() elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos), Vector.ones() if pos.x + width <= touch.x <= top_right.x - width: if pos.y <= touch.y <= pos.y + width: return Vector(pos.x, pos.y + size.y), Vector(0.0, 1.0) elif top_right.y - width <= touch.y <= top_right.y: return Vector(pos), Vector(0.0, 1.0) if pos.y + width <= touch.y <= top_right.y - width: if pos.x <= touch.x <= pos.x + width: return Vector(pos.x + size.x, pos.y), Vector(1.0, 0.0) elif top_right.x - width <= touch.x <= top_right.x: return Vector(pos), Vector(1.0, 0.0)
def _update_on_boundaries_change(self, *args): self.set_size_and_pos(Vector(self.size), Vector(self.pos))
def _get_uvs_size(self): tex_size = self.texture_size tz_size = tex_size.safe_div(self.container_size) * Vector(self.size) return tz_size.safe_div(tex_size)
def set_size(self, new_size): self.size[:] = Vector.clamp( new_size, Vector(self.size_min), Vector(self.pos_limit[2:]) - Vector(self.pos_limit[:2]))
def set_defaults(self): self.set_size(Vector(self.default_size)) self._set_zoom_pos_from_hint(Vector(self.default_pos_hint)) self.set_pos(Vector(self.zoom_pos))
def collide_with_inner_box(self, x, y): width = Vector.ones() * self.current_collide_border_width pos = Vector(self.pos) return Vector.in_bbox((x, y), pos + width, pos + Vector(self.size) - width)
def _set_zoom_pos_from_hint(self, hint): zoom_pos_limit = self._get_zoom_pos_limit() pos = zoom_pos_limit[0] top_right = zoom_pos_limit[1] - Vector(self.size) zoom_pos = (top_right - pos) * hint + pos self._set_zoom_pos(zoom_pos)