-- this file is part of reno desktop. -- -- reno desktop 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. -- -- reno desktop 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 reno desktop. if not, see . -- renotk (formerly awmtk2) - template/granular styling library for reno local wibox = require("wibox") local awful = require("awful") local gears = require("gears") local beautiful = require("beautiful") beautiful.widgets = beautiful.widgets or {} beautiful.templates = beautiful.templates or {} local awmtk = {} -- {{{ utils awmtk.create_delta = function(name,instance_delta,class_delta,parent_delta) -- to save memory, we create proxies for lower layers called "deltas" -- this function creates that proxy layer using metatables -- fun story - i used instance_delta instead of {} at first. -- the results were horrifying and confusing. return setmetatable({},{ __index = function(self,k) -- per-instance overrides are top priority if rawget(instance_delta,k) then return rawget(instance_delta,k) -- class-wide overrides are second in priority elseif type(class_delta[name]) == "table" and rawget(class_delta[name],k) then return rawget(class_delta[name],k) -- parent is fallback elseif parent_delta[k] then return parent_delta[k] end end }) end awmtk.create_style = function(name,parent,overrides) -- a style is essentially a layer of deltas upon the previous (parent) style local new_style = {} local odelta = (overrides and overrides[name]) or {} local cdelta = beautiful.widgets[name] or {} for name,parent_class in pairs(parent) do new_style[name] = awmtk.create_delta( name, odelta, cdelta, parent_class ) end return new_style end awmtk.create_template_lib = function(name,parent,overrides) -- same thing but beautiful.templates return awmtk.create_delta( name, overrides or {}, beautiful.templates or {}, parent ) end awmtk.build_templates = function(templates,style) local new_templates = {} for name,template in pairs(awmtk.proto_templates) do new_templates[name] = templates[name](style) end return new_templates end awmtk.mask_object_call = function(obj,call) local new = {} for k,v in pairs(obj) do new[k] = v end return setmetatable(new,{ __index = obj, __call = call }) end awmtk.wrap_hooks = function(w,callbacks) -- attach hooks to function local mcall = (getmetatable(w) and getmetatable(w).__call) or w local call_wrapper = function(...) if callbacks and callbacks.on_create_pre then callbacks.on_create_pre(...) end local widget = mcall(...) if callbacks and callbacks.on_create then callbacks.on_create(widget,...) end if callbacks and callbacks.on_ready then callbacks._on_ready_called = false local func = function() if not callbacks._on_ready_called then callbacks.on_ready(widget) callbacks._on_ready_called = true end end widget:connect_signal("widget::layout_changed",func) widget:connect_signal("widget::layout_changed",function() widget:disconnect_signal("widget::layout_changed",func) end) end return widget end return (getmetatable(w) and awmtk.mask_object_call(w,call_wrapper)) or call_wrapper end awmtk.merge = gears.table.join -- }}} -- {{{ default style -- prototype style -- notice that it's not awmtk.default style - it's used as a base for default. awmtk.proto_style = { base = setmetatable({ -- { backgrounds -- custom background color for highlighting elements bg_highlight = beautiful.bg_highlight or beautiful.bg_focus, -- allow more complex themes to define background images bgimage_focus = beautiful.bgimage_focus, bgimage_normal = beautiful.bgimage_normal, -- } -- { borders -- borders for popups shape_border_width = beautiful.shape_border_width or 0, shape_border_color = beautiful.shape_border_color or beautiful.bg_normal, -- } -- { callbacks -- a tiny bit more complex thing to account for more extensibility -- the stub functions do nothing - you should implement functionality inside theme onpress = function() end, onrelease = function() end, -- } -- { shapes margins = 5, spacing = 5, shape = function(cr, width, height) return require("gears").shape.rounded_rect(cr,width,height,5) end, -- } },{__index = beautiful}) } -- subclasses awmtk.proto_style.container = awmtk.create_delta("container",{ },awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.button = awmtk.create_delta("button",{ margins = 3 },awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.icon = awmtk.create_delta("icon",{ margins = 1 },awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.textbox = awmtk.create_delta("textbox",{ font = beautiful.font or "sans 8" }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.separator = awmtk.create_delta("separator",{ thickness = 1, color = beautiful.bg_focus, border_width = 0 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.article = awmtk.create_delta("article", { icon_size = 16, small_font = beautiful.small_font or beautiful.font, font_align = "left", small_font_align = "left" }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.popup = awmtk.create_delta("popup", { }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.titlebar = awmtk.create_delta("titlebar", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.wibar = awmtk.create_delta("wibar", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.menu = awmtk.create_delta("menu", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.center = awmtk.create_delta("center", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.slider = awmtk.create_delta("slider", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) awmtk.proto_style.checkbox = awmtk.create_delta("checkbox", { }, awmtk.proto_style,awmtk.proto_style.base) -- }}} -- {{{ generic templates awmtk.proto_templates = { -- templates are built first using the given style, then applied to contents -- through returned function container = function(style) -- container is practically any "box" that contains buttons, textboxes, -- and anything you want to put into the container. do not confuse with -- popup - containers are designed to separate contents within a popup. return function(layout,options) return awmtk.merge({ { layout, margins = style.container.margins, layout = wibox.container.margin }, bgimage = style.container.bgimage_normal, bg = style.container.bg_normal, fg = style.container.fg_normal, shape = style.container.shape, shape_border_color = style.container.shape_border_color, shape_border_width = style.container.shape_border_width, widget = awmtk.wrap_hooks(wibox.container.background,options) },options or {}) end end, button = function(style) -- self explanatory. notice that this does not bear any function - -- only the visual part of the button. by design, onpress and onrelease -- callbacks should be connected to button events for animations return function(layout,options) return awmtk.merge({ { layout, margins = style.button.margins, layout = wibox.container.margin }, bgimage = style.button.bgimage_normal, bg = style.button.bg_normal, fg = style.button.fg_normal, shape = style.button.shape, shape_border_color = style.button.shape_border_color, shape_border_width = style.button.shape_border_width, widget = awmtk.wrap_hooks(wibox.container.background,options) },options or {}) end end, textbox = function(style) -- nothing fancy here, but you can set per-widget fonts using beautiful. return function(options) return awmtk.merge({ font = style.textbox.font, widget = awmtk.wrap_hooks(wibox.widget.textbox,options) },options or {}) end end, hseparator = function(style) -- wow, i guess? return function(options) return awmtk.merge({ widget = awmtk.wrap_hooks(wibox.widget.separator,options), orientation = "horizontal", thickness = style.separator.thickness, color = style.separator.color, border_width = style.separator.border_width },options or {}) end end, vseparator = function(style) -- i'm running out of comments return function(options) return awmtk.merge({ widget = awmtk.wrap_hooks(wibox.widget.separator,options), orientation = "vertical", thickness = style.separator.thickness, color = style.separator.color, border_width = style.separator.border_width },options or {}) end end, article = function(style) -- article is a template that combines 3 common pieces of a full item: -- icon, name and description. designed to be placed within a container -- or a button. return function(options) return awmtk.merge({ (options.icon and { { { image = options.icon, id = options.icon_id, resize = options.resize, widget = wibox.widget.imagebox }, strategy = "exact", height = options.icon_size or style.article.icon_size, width = options.icon_size or style.article.icon_size, widget = wibox.container.constraint }, widget = wibox.container.place, valign = "center", halign = "center" }), { { markup = options.title or "", id = options.title_id, widget = wibox.widget.textbox, font = style.article.font, valign = style.article.title_valign or "center", align = style.article.title_align or "left" }, (options.description and { markup = options.description or "", id = options.desc_id, widget = wibox.widget.textbox, font = style.article.small_font, valign = style.article.desc_valign or "center", align = style.article.desc_align or "left" }), spacing = style.article.spacing, layout = wibox.layout.flex.vertical }, spacing = style.article.spacing, layout = awmtk.wrap_hooks( wibox.layout.fixed.horizontal, options ) }, options or {}) end end, center = function(style) return function(layout,options) options = options or {} return awmtk.merge({ { layout, strategy = "exact", height = options.height or style.center.height, width = options.width or style.center.width, widget = wibox.container.constraint }, widget = awmtk.wrap_hooks( wibox.container.place, options ), valign = "center", halign = "center" },options or {}) end end, popup = function(style) -- popup is a distinct template designed to accomodate the "root" of -- a popup, allowing one to add titlebars to popups, for example. return function(widget,options) return awmtk.merge({ widget = { widget, margins = style.popup.margins, layout = awmtk.wrap_hooks(wibox.container.margin,options) }, bgimage = style.popup.bgimage_normal, shape = style.popup.shape, visible = false, ontop = true },options or {}) end end, titlebar = function(style) -- titlebar is a separate class specifically for window and popup -- titlebars. the decision to make it a separate class was due to -- the fact that much customization is done through default theme table return function(layout,options) -- if there's one thing that fascinates me, it's how much weird -- bugs i manage to uncover by some sort of miraculous accident. -- this one fixes a race condition in margins+(left/right/bottom/top) configuration scenario local margins = style.titlebar.margins if (style.titlebar.left or style.titlebar.right or style.titlebar.bottom or style.titlebar.top) then margins = nil end return awmtk.merge({ layout, margins = margins, layout = awmtk.wrap_hooks(wibox.container.margin,options), left = style.titlebar.left, right = style.titlebar.right, bottom = style.titlebar.bottom, top = style.titlebar.top },options or {}) end end, wibar = function(style) -- just you regular old wibar, but as a style template. return function(layout,options) local margins = style.wibar.margins if (style.wibar.left or style.wibar.right or style.wibar.bottom or style.wibar.top) then margins = nil end return awmtk.merge({ layout, margins = margins, layout = awmtk.wrap_hooks(wibox.container.margin,options), left = style.wibar.left, right = style.wibar.right, bottom = style.wibar.bottom, top = style.wibar.top },options or {}) end end, slider = function(style) -- slider widget but wired to work with the awmtk2 namespace system return function(args) return awmtk.merge({ handle_shape = style.slider.handle_shape or style.slider.shape, handle_color = style.slider.bg_normal, handle_margins = style.slider.handle_margins, handle_width = style.slider.handle_width, handle_border_color = style.slider.handle_border_color, handle_border_width = style.slider.handle_border_width, bar_shape = style.slider.bar_shape or style.slider.shape, bar_height = style.slider.bar_height, bar_color = style.slider.bg_focus, bar_margins = style.slider.bar_margins, bar_border_width = style.slider.bar_border_width, bar_border_color = style.slider.bar_border_color, forced_width = style.slider.width, forced_height = style.slider.height, widget = awmtk.wrap_hooks(wibox.widget.slider,args) },args or {}) end end, checkbox = function(style) return function(args) return awmtk.merge({ color = style.checkbox.color or style.checkbox.bg_normal, paddings = style.checkbox.paddings, shape = style.checkbox.shape, border_width = style.checkbox.border_width, border_color = style.checkbox.border_color or style.checkbox.bg_normal, bg = style.checkbox.bg or style.checkbox.bg_focus, check_color = style.checkbox.check_color or style.checkbox.bg_normal, check_shape = style.checkbox.check_shape, widget = awmtk.wrap_hooks(wibox.widget.checkbox,args) },args) end end } -- last but not least - we export a default template lib and default style. -- this is done in order to allow overriding default style behaviour from theme awmtk.default = awmtk.create_style("default",awmtk.proto_style,{}) awmtk.templates = awmtk.create_template_lib("templates",awmtk.proto_templates,{}) -- generic styles for widgets that need them awmtk.generic = {} awmtk.generic.menu = awmtk.create_style("generic_menu",awmtk.default,{}) awmtk.generic.button_list = awmtk.create_style("generic_button_list",awmtk.default,{}) awmtk.generic.iconified_widget = awmtk.create_style("generic_iconified_widget",awmtk.default,{}) awmtk.generic.status_widget = awmtk.create_style("generic_status_widget",awmtk.default,{}) awmtk.generic.oneline_widget = awmtk.create_style("generic_oneline_widget",awmtk.default,{}) awmtk.generic.composite_widget = awmtk.create_style("generic_composite_widget",awmtk.default,{}) awmtk.generic.popup = awmtk.create_style("generic_popup",awmtk.default,{}) -- }}} return awmtk