awesome/lib/wibox/widget/slider.lua

748 lines
21 KiB
Lua

---------------------------------------------------------------------------
-- An interactive mouse based slider widget.
--
--@DOC_wibox_widget_defaults_slider_EXAMPLE@
--
-- @author Grigory Mishchenko <grishkokot@gmail.com>
-- @author Emmanuel Lepage Vallee <elv1313@gmail.com>
-- @copyright 2015 Grigory Mishchenko, 2016 Emmanuel Lepage Vallee
-- @widgetmod wibox.widget.slider
-- @supermodule wibox.widget.base
---------------------------------------------------------------------------
local setmetatable = setmetatable
local type = type
local color = require("gears.color")
local gtable = require("gears.table")
local beautiful = require("beautiful")
local base = require("wibox.widget.base")
local shape = require("gears.shape")
local capi = {
mouse = mouse,
mousegrabber = mousegrabber,
root = root,
}
local slider = {mt={}}
--- The slider handle shape.
--
--@DOC_wibox_widget_slider_handle_shape_EXAMPLE@
--
-- @property handle_shape
-- @tparam shape|nil handle_shape
-- @propemits true false
-- @propbeautiful
-- @see gears.shape
--- The slider handle widget.
--
--@DOC_wibox_widget_slider_handle_widget_EXAMPLE@
--
-- @property handle_widget
-- @tparam[opt=nil] widget|nil handle_widget The handle widget
--- The slider handle color.
--
--@DOC_wibox_widget_slider_handle_color_EXAMPLE@
--
-- @property handle_color
-- @propbeautiful
-- @tparam color|nil handle_color
-- @propemits true false
--- The slider handle margins.
--
--@DOC_wibox_widget_slider_handle_margins_EXAMPLE@
--
-- @property handle_margins
-- @tparam[opt={}] table|number|nil handle_margins
-- @tparam[opt=0] number handle_margins.left
-- @tparam[opt=0] number handle_margins.right
-- @tparam[opt=0] number handle_margins.top
-- @tparam[opt=0] number handle_margins.bottom
-- @propertyunit pixel
-- @propertytype number A single value used for all sides.
-- @propertytype table A different value for each side. The side names are:
-- @negativeallowed true
-- @propemits true false
-- @propbeautiful
--- The slider handle width.
--
--@DOC_wibox_widget_slider_handle_width_EXAMPLE@
--
-- @property handle_width
-- @tparam number|nil handle_width
-- @negativeallowed false
-- @propertyunit pixel
-- @propemits true false
-- @propbeautiful
--- The handle border_color.
--
--@DOC_wibox_widget_slider_handle_border_EXAMPLE@
--
-- @property handle_border_color
-- @tparam color|nil handle_border_color
-- @propemits true false
-- @propbeautiful
--- The handle border width.
-- @property handle_border_width
-- @tparam[opt=0] number|nil handle_border_width
-- @propertyunit pixel
-- @negativeallowed false
-- @propemits true false
-- @propbeautiful
--- The bar (background) shape.
--
--@DOC_wibox_widget_slider_bar_shape_EXAMPLE@
--
-- @property bar_shape
-- @tparam shape|nil bar_shape
-- @propemits true false
-- @propbeautiful
-- @see gears.shape
--- The bar widget
--
--@DOC_wibox_widget_slider_bar_widget_EXAMPLE@
--
-- @property bar_widget
-- @tparam[opt=nil] widget|nil bar_widget The widget that spans the active bar segment
--- The bar (background) height.
--
--@DOC_wibox_widget_slider_bar_height_EXAMPLE@
--
-- @property bar_height
-- @tparam number|nil bar_height
-- @propertyunit pixel
-- @negativeallowed false
-- @propbeautiful
-- @propemits true false
--- The bar (background) color.
--
--@DOC_wibox_widget_slider_bar_color_EXAMPLE@
--
-- @property bar_color
-- @tparam color|nil bar_color
-- @propbeautiful
-- @propemits true false
--- The bar (active) color.
--
--@DOC_wibox_widget_slider_bar_active_color_EXAMPLE@
--
-- Only works when both `bar_active_color` and `bar_color` are passed as hex color string
-- @property bar_active_color
-- @tparam color|nil bar_active_color
-- @propbeautiful
-- @propemits true false
--- The bar (background) margins.
--
--@DOC_wibox_widget_slider_bar_margins_EXAMPLE@
--
-- @property bar_margins
-- @tparam[opt={}] table|number|nil bar_margins
-- @tparam[opt=0] number bar_margins.left
-- @tparam[opt=0] number bar_margins.right
-- @tparam[opt=0] number bar_margins.top
-- @tparam[opt=0] number bar_margins.bottom
-- @propertyunit pixel
-- @propertytype number A single value used for all sides.
-- @propertytype table A different value for each side. The side names are:
-- @negativeallowed true
-- @propbeautiful
-- @propemits true false
--- The bar (background) border width.
-- @property bar_border_width
-- @tparam[opt=0] number|nil bar_border_width
-- @propertyunit pixel
-- @negativeallowed false
-- @propemits true false
-- @propbeautiful
--- The bar (background) border_color.
--
--@DOC_wibox_widget_slider_bar_border_EXAMPLE@
--
-- @property bar_border_color
-- @tparam color|nil bar_border_color
-- @propbeautiful
-- @propemits true false
--- The slider value.
--
--@DOC_wibox_widget_slider_value_EXAMPLE@
--
-- @property value
-- @tparam[opt=0] number value
-- @negativeallowed true
-- @propemits true false
--- The slider minimum value.
--
-- @property minimum
-- @tparam[opt=0] number minimum
-- @negativeallowed true
-- @propemits true false
--- The slider maximum value.
--
-- @property maximum
-- @tparam[opt=100] number maximum
-- @negativeallowed true
-- @propemits true false
--- The bar (background) border width.
--
-- @beautiful beautiful.slider_bar_border_width
-- @param number
--- The bar (background) border color.
--
-- @beautiful beautiful.slider_bar_border_color
-- @param color
--- The handle border_color.
--
-- @beautiful beautiful.slider_handle_border_color
-- @param color
--- The handle border width.
--
-- @beautiful beautiful.slider_handle_border_width
-- @param number
--- The handle width.
--
-- @beautiful beautiful.slider_handle_width
-- @param number
--- The handle color.
--
-- @beautiful beautiful.slider_handle_color
-- @param color
--- The handle shape.
--
-- @beautiful beautiful.slider_handle_shape
-- @tparam[opt=gears.shape.rectangle] gears.shape shape
-- @see gears.shape
--- The bar (background) shape.
--
-- @beautiful beautiful.slider_bar_shape
-- @tparam[opt=gears.shape.rectangle] gears.shape shape
-- @see gears.shape
--- The bar (background) height.
--
-- @beautiful beautiful.slider_bar_height
-- @param number
--- The bar (background) margins.
--
-- @beautiful beautiful.slider_bar_margins
-- @tparam[opt={}] table margins
-- @tparam[opt=0] number margins.left
-- @tparam[opt=0] number margins.right
-- @tparam[opt=0] number margins.top
-- @tparam[opt=0] number margins.bottom
--- The slider handle margins.
--
-- @beautiful beautiful.slider_handle_margins
-- @tparam[opt={}] table margins
-- @tparam[opt=0] number margins.left
-- @tparam[opt=0] number margins.right
-- @tparam[opt=0] number margins.top
-- @tparam[opt=0] number margins.bottom
--- The bar (background) color.
--
-- @beautiful beautiful.slider_bar_color
-- @param color
--- The bar (active) color.
--
-- Only works when both `beautiful.slider_bar_color` and `beautiful.slider_bar_active_color` are hex color strings
-- @beautiful beautiful.slider_bar_active_color
-- @param color
local properties = {
-- Handle
handle_shape = shape.rectangle,
handle_widget = false,
handle_color = false,
handle_margins = {},
handle_width = false,
handle_border_width = 0,
handle_border_color = false,
-- Bar
bar_shape = shape.rectangle,
bar_height = false,
bar_color = false,
bar_active_color = false,
bar_margins = {},
bar_border_width = 0,
bar_border_color = false,
bar_widget = false,
-- Content
value = 0,
minimum = 0,
maximum = 100,
}
-- 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, value)
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
--- Set the handle widget
--
-- @method set_handle_widget
-- @tparam[opt=nil] widget|nil bar_widget Set the handle widget
-- @noreturn
function slider:set_handle_widget(value)
local changed = self._private.handle_widget ~= value
self._private.handle_widget = value
if changed then
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end
end
--- Set the bar widget
--
-- @method set_bar_widget
-- @tparam[opt=nil] widget|nil bar_widget Set the widget that spans the active bar segment
-- @noreturn
function slider:set_bar_widget(value)
local changed = self._private.bar_widget ~= value
self._private.bar_widget = value
if changed then
self:emit_signal("widget::redraw_needed")
self:emit_signal("widget::layout_changed")
end
end
-- Add some validation to set_value
--- Set the slider's value
-- @tparam number number Set value to number
-- @method set_value
-- @noreturn
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", value)
self:emit_signal( "widget::redraw_needed" )
if self._private.handle_widget or self._private.bar_widget then
self:emit_signal("widget::layout_changed")
end
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
-- TODO add single number param to margin setters
--- Set the slider's margins
--
-- @tparam[opt={}] table|number|nil handle_margins
-- @tparam[opt=0] number handle_margins.left
-- @tparam[opt=0] number handle_margins.right
-- @tparam[opt=0] number handle_margins.top
-- @tparam[opt=0] number handle_margins.bottom
-- @propemits true false
-- @propbeautiful
-- @method set_handle_margins
-- @noreturn
function slider:set_handle_margins(value)
local value = value or 0
if type(value) == "number" then
value = {
top = value,
bottom = value,
left = value,
right = value,
}
end
self._private.handle_margins = value
self:emit_signal( "property::handle_margins", value)
self:emit_signal( "widget::redraw_needed" )
self:emit_signal("widget::layout_changed")
end
--- Set the bar's margins
--
-- @tparam[opt={}] table|number|nil bar_margins
-- @tparam[opt=0] number bar_margins.left
-- @tparam[opt=0] number bar_margins.right
-- @tparam[opt=0] number bar_margins.top
-- @tparam[opt=0] number bar_margins.bottom
-- @propemits true false
-- @propbeautiful
-- @method set_bar_margins
-- @noreturn
function slider:set_bar_margins(value)
if type(value) == "number" then
value = {
top = value,
bottom = value,
left = value,
right = value,
}
end
self._private.bar_margins = value
self:emit_signal( "property::bar_margins", value)
self:emit_signal( "widget::redraw_needed" )
self:emit_signal("widget::layout_changed")
end
function slider:draw(_, cr, width, height)
local value = self._private.value or self._private.min or 0
local maximum = self._private.maximum
or properties.maximum
local minimum = self._private.minimum
or properties.minimum
local range = maximum - minimum
local active_rate = (value - minimum) / range
local handle_height, handle_width = height, self._private.handle_width
or beautiful.slider_handle_width
or math.floor(height/2)
local handle_border_width = self._private.handle_border_width
or beautiful.slider_handle_border_width
or properties.handle_border_width or 0
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
local bar_active_color = self._private.bar_active_color
or beautiful.slider_bar_active_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
bar_height = bar_height or (
height - (margins.top) - (margins.bottom)
)
x_offset, y_offset = margins.left,
margins.top
right_margin = margins.right
else
bar_height = bar_height or beautiful.slider_bar_height or height
y_offset = math.floor((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_active_color and type(bar_color) == "string" and type(bar_active_color) == "string" then
local bar_active_width = math.floor(
active_rate * (width - x_offset - right_margin)
- (handle_width - handle_border_width/2) * (active_rate - 0.5)
)
cr:set_source(color.create_pattern{
type = "linear",
from = {0,0},
to = {bar_active_width, 0},
stops = {{0.99, bar_active_color}, {0.99, bar_color}}
})
end
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
cr:set_line_width(bar_border_width)
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_shape = self._private.handle_shape
or beautiful.slider_handle_shape
or properties.handle_shape
-- Lets get the margins for the handle
margins = self._private.handle_margins
or beautiful.slider_handle_margins
x_offset, y_offset = 0, 0
if margins then
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
-- Get the widget size back to it's non-transfored value
local min, _, interval = get_extremums(self)
local rel_value = math.floor(((value-min)/interval) * (width-handle_width))
cr:translate(x_offset + rel_value, y_offset)
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:set_line_width(handle_border_width)
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
function slider:layout(context, width, height)
local result = {}
local handle_widget = self._private.handle_widget
or nil
local bar_widget = self._private.bar_widget
or nil
--[[ if not handle_widget or not bar_widget then
return {}
end ]]
local value = self._private.value or self._private.min or 0
local min, _, interval = get_extremums(self)
local bar_height, bar_width = self._private.bar_height or height, width
local handle_height, handle_width = height, self._private.handle_width
or beautiful.slider_handle_width
or width
if bar_widget then
bar_width = (((value - min) / interval) * width + handle_width / 2 - ((value - min) / interval ) * handle_width)
local w, h = base.fit_widget(self, context, bar_widget, bar_width, bar_height)
local margins = self._private.bar_margins
or beautiful.slider_bar_margins
local x_offset, y_offset = 0, 0
if margins then
x_offset, y_offset = margins.left, margins.top
bar_width = bar_width -
(margins.left) - (margins.right)
end
local x, y = 0 + x_offset, 0 + y_offset
table.insert(result, base.place_widget_at(bar_widget, x, y, w, h))
end
if handle_widget then
local w, h = base.fit_widget(self, context, handle_widget, handle_width, handle_height)
local margins = self._private.handle_margins
or beautiful.slider_handle_margins
local x_offset, y_offset = 0, 0
if margins then
x_offset, y_offset = margins.left, margins.top
handle_width = handle_width -
(margins.left) - (margins.right)
end
x_offset = x_offset + handle_width / 2 - w / 2
local x = (((value - min) / interval) * (width - handle_width) + x_offset)
local y = (height / 2) + y_offset - h / 2
table.insert(result, base.place_widget_at(handle_widget, x, y, w, h))
end
return result
end
-- Move the handle to the correct location
local function move_handle(self, width, x, _)
local _, _, interval = get_extremums(self)
self:set_value(math.floor((x*interval)/width))
end
local function mouse_press(self, x, y, button_id, _, geo)
if button_id ~= 1 then return end
local matrix_from_device = geo.hierarchy:get_matrix_from_device()
-- Sigh. geo.width/geo.height is in device space. We need it in our own
-- coordinate system
local width = geo.widget_width
move_handle(self, width, x, y)
-- Calculate a matrix transforming from screen coordinates into widget coordinates
local wgeo = geo.drawable.drawable:geometry()
local matrix = matrix_from_device:translate(-wgeo.x, -wgeo.y)
capi.mousegrabber.run(function(mouse)
if not mouse.buttons[1] then
return false
end
-- Calculate the point relative to the widget
move_handle(self, width, matrix:transform_point(mouse.x, mouse.y))
return true
end,"fleur")
end
--- Create a slider widget.
--
-- @constructorfct wibox.widget.slider
-- @tparam[opt={}] table args
-- @tparam[opt] gears.shape args.handle_shape The slider handle shape.
-- @tparam[opt] widget args.handle_widget The slider handle widget.
-- @tparam[opt] color args.handle_color The slider handle color.
-- @tparam[opt] table args.handle_margins The slider handle margins.
-- @tparam[opt] number args.handle_width The slider handle width.
-- @tparam[opt] color args.handle_border_color The handle border_color.
-- @tparam[opt] number args.handle_border_width The handle border width.
-- @tparam[opt] gears.shape args.bar_shape The bar (background) shape.
-- @tparam[opt] widget args.bar_widget The active bar widget.
-- @tparam[opt] number args.bar_height The bar (background) height.
-- @tparam[opt] color args.bar_color The bar (background) color.
-- @tparam[opt] color args.bar_active_color The bar (active) color.
-- @tparam[opt] table args.bar_margins The bar (background) margins.
-- @tparam[opt] number args.bar_border_width The bar (background) border width.
-- @tparam[opt] color args.bar_border_color The bar (background) border_color.
-- @tparam[opt] number args.value The slider value.
-- @tparam[opt] number args.minimum The slider minimum value.
-- @tparam[opt] number args.maximum The slider maximum value.
local function new(args)
local ret = base.make_widget(nil, nil, {
enable_properties = true,
})
gtable.crush(ret, slider, true)
gtable.crush(ret, args or {})
ret:connect_signal("button::press", mouse_press)
return ret
end
function slider.mt:__call(...)
return new(...)
end
return setmetatable(slider, slider.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80