radical/widgets/slider.lua

372 lines
10 KiB
Lua
Raw Normal View History

---------------------------------------------------------------------------
-- An interactive mouse based slider widget.
--
-- @author Grigory Mishchenko <grishkokot@gmail.com>
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
-- @copyright 2015 Grigory Mishchenko, 2016 Emmanuel Lepage Vallee
-- @release @AWESOME_VERSION@
-- @classmod wibox.widget.slider
---------------------------------------------------------------------------
local setmetatable = setmetatable
local type = type
local color = require("gears.color")
local util = require("awful.util")
local beautiful = require("beautiful")
local base = require("wibox.widget.base")
local shape = require("gears.shape")
local gmatrix = require("gears.matrix")
local capi = {
mouse = mouse,
mousegrabber = mousegrabber,
root = root,
}
local slider = {mt={}}
local properties = {
-- Handle
handle_shape = shape.rectangle,
handle_color = false,
handle_margins = {},
handle_width = false,
handler_border_width = 0,
handler_border_color = false,
-- Bar
bar_shape = shape.rectangle,
bar_height = false,
bar_color = false,
bar_margins = {},
bar_border_width = 0,
bar_border_color = false,
-- Content
value = 0,
minimum = 0,
maximum = 100,
-- Other
cursor = "fleur",
}
-- Create the accessors
for prop in pairs(properties) do
slider["set_"..prop] = function(self, value)
local changed = self._private[prop] ~= value
self._private[prop] = value
if changed then
self:emit_signal("property::"..prop)
self:emit_signal("widget::redraw_needed")
end
end
slider["get_"..prop] = function(self)
-- Ignoring the false's is on purpose
return self._private[prop] == nil
and properties[prop]
or self._private[prop]
end
end
-- Add some validation to set_value
function slider:set_value(value)
value = math.min(value, self:get_maximum())
value = math.max(value, self:get_minimum())
local changed = self._private.value ~= value
self._private.value = value
if changed then
self:emit_signal( "property::value" )
self:emit_signal( "widget::redraw_needed" )
end
end
local function get_extremums(self)
local min = self._private.minimum or properties.minimum
local max = self._private.maximum or properties.maximum
local interval = max - min
return min, max, interval
end
local function get_value(self, width, x)
local _, _, interval = get_extremums(self)
return math.floor((x*interval)/width)
end
function slider:draw(_, cr, width, height)
local bar_height = self._private.bar_height
-- If there is no background, then skip this
local bar_color = self._private.bar_color
or beautiful.slider_bar_color
if bar_color then
cr:set_source(color(bar_color))
end
local margins = self._private.bar_margins
or beautiful.slider_bar_margins
local x_offset, right_margin, y_offset = 0, 0
if margins then
if type(margins) == "number" then
bar_height = bar_height or (height - 2*margins)
x_offset, y_offset = margins, margins
right_margin = margins
else
bar_height = bar_height or (height - margins.top - margins.bottom)
x_offset, y_offset = margins.left, margins.top
right_margin = margins.right
end
else
bar_height = bar_height or beautiful.slider_bar_height or height
y_offset = (height - bar_height)/2
end
cr:translate(x_offset, y_offset)
local bar_shape = self._private.bar_shape
or beautiful.slider_bar_shape
or properties.bar_shape
local bar_border_width = self._private.bar_border_width
or beautiful.slider_bar_border_width
or properties.bar_border_width
bar_shape(cr, width - x_offset - right_margin, bar_height or height)
if bar_color then
if bar_border_width == 0 then
cr:fill()
else
cr:fill_preserve()
end
end
-- Draw the bar border
if bar_border_width > 0 then
local bar_border_color = self._private.bar_border_color
or beautiful.slider_bar_border_color
or properties.bar_border_color
if bar_border_color then
cr:save()
cr:set_source(color(bar_border_color))
cr:stroke()
cr:restore()
else
cr:stroke()
end
end
cr:translate(-x_offset, -y_offset)
-- Paint the handle
local handle_color = self._private.handle_color
or beautiful.slider_handle_color
-- It is ok if there is no color, it will be inherited
if handle_color then
cr:set_source(color(handle_color))
end
local handle_height, handle_width = height, self._private.handle_width
or beautiful.slider_handle_width
or height/2
local handle_shape = self._private.handle_shape
or beautiful.slider_handle_shape
or properties.handle_shape
-- Lets get the margins for the handle
local margins = self._private.bar_margins
or beautiful.slider_bar_margins
local x_offset, y_offset = 0, 0
if margins then
if type(margins) == "number" then
x_offset, y_offset = margins, margins
handle_width = handle_width - 2*margins
handle_height = handle_height - 2*margins
else
x_offset, y_offset = margins.left, margins.top
handle_width = handle_width - margins.left - margins.right
handle_height = handle_height - margins.top - margins.bottom
end
end
local value = self._private.value or self._private.min or 0
-- Get the widget size back to it's non-transfored value
--local matrix = gmatrix.from_cairo_matrix(cr:get_matrix())
local min, _, interval = get_extremums(self)
local rel_value = ((value-min)/interval) * (width-handle_width)
cr:translate(x_offset + rel_value, y_offset)
local handle_border_width = self._private.handle_border_width
or beautiful.slider_handle_border_width
or properties.handle_border_width
handle_shape(cr, handle_width, handle_height)
if handle_border_width > 0 then
cr:fill_preserve()
else
cr:fill()
end
-- Draw the handle border
if handle_border_width > 0 then
local handle_border_color = self._private.handle_border_color
or beautiful.slider_handle_border_color
or properties.handle_border_color
if handle_border_color then
cr:set_source(color(handle_border_color))
end
cr:stroke()
end
end
function slider:fit(_, width, height)
-- Use all the space, this should be used with a constraint widget
return width, height
end
local event = {}
function event.mouse_enter(self)
self:emit_signal("widget::redraw_needed") --TODO
local cursor = self._private.cursor or "fleur"
capi.root.cursor(cursor)
end
function event.mouse_leave(self)
self:emit_signal("widget::redraw_needed") --TODO
-- capi.root.cursor("fleur")
end
local rect_order = {"x", "y", "width", "height"}
-- Move the handle to the correct location
local function move_handle(self, x, y, geo)
-- Fix the X position when used in a rotate or mirror widget
-- Remove the translation to bring {x, y} and the rect on the same origin
local rem_translate = geo.matrix_to_device*gmatrix.identity
rem_translate.x0, rem_translate.y0 = 0, 0
local rect = {rem_translate:transform_rectangle(
0,
0,
geo.width,
geo.height
)}
-- Floating point precision is here both too much and not enough
for k, v in ipairs(rect) do
rect[rect_order[k]] = util.round(v)
end
local px = rem_translate:transform_point(x, y)
-- If the transformation moved the rectangle away from {0,0}, compensate
if rect.x < 0 then
px = rect.width + px
end
-- --HACK Cairo 0,0 is top left, a matrix is bottom left, funny...
if util.round(rem_translate:transform_point(0, 100)) ~= 0 then
px = -(px - rect.width)
end
self:set_value(get_value(self, rect.width, px))
self:emit_signal("widget::redraw_needed")
end
function event.mouse_press(self, x, y, button_id, _, geo)
if button_id ~= 1 then return end
move_handle(self, x, y, geo)
-- Get the "real" position of the widget
local wgeo = geo.drawable.drawable:geometry()
local wx = wgeo.x + geo.x
local wy = wgeo.y + geo.y
capi.mousegrabber.run(function(mouse)
if not mouse.buttons[1] then
self:emit_signal("widget::redraw_needed")
return false
end
-- Get the closest point to the widget
local abs_x, abs_y = mouse.x, mouse.y
abs_x = abs_x > (wx+geo.width) and wx+geo.width or (
abs_x < wx and wx
) or abs_x
abs_y = abs_y > (wy+geo.height) and wy+geo.height or (
abs_y < wy and wy
) or abs_y
-- Get the relative point
local rx, ry = abs_x - wx, abs_y - wy
move_handle(self, rx, ry, geo)
return true
end, self:get_cursor())
end
--- Create a slider widget.
-- @tparam[opt={}] table args
-- @function wibox.widget.slider
local function new(args)
local ret = base.make_widget(nil, nil, {
enable_properties = true,
})
util.table.crush(ret._private, args or {})
util.table.crush(ret, slider, true)
-- Change the cusor and redraw (in case there is hover and pressed colors)
ret:connect_signal("mouse::enter" , event.mouse_enter)
ret:connect_signal("mouse::leave" , event.mouse_leave)
ret:connect_signal("button::press", event.mouse_press)
return ret
end
function slider.mt:__call(...)
return new(...)
end
--FIXME This is still broken...
--[[w:setup{
{
{
widget = wibox.widget.slider,
},
direction = "east",
widget = wibox.container.rotate
},
reflection = {
horizontal = true
},
widget = wibox.container.mirror
}]]
return setmetatable(slider, slider.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80