From a1918b8306f3a03a38dbc5f33c2151abc5c8fdea Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Tue, 23 Oct 2012 20:32:59 +0200 Subject: [PATCH] awful.titlebars: Implement This commits adds the necessary lua code so that we finally can have titlebars. As the baseline for the needed functionality, the titlebar code in awesome 3.4 and a quick poll on the mailing list were used. Signed-off-by: Uli Schlachter --- awesomerc.lua.in | 33 ++++++ lib/awful/init.lua.in | 1 + lib/awful/titlebar.lua.in | 235 ++++++++++++++++++++++++++++++++++++++ lib/wibox/drawable.lua.in | 3 +- 4 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 lib/awful/titlebar.lua.in diff --git a/awesomerc.lua.in b/awesomerc.lua.in index e9047671b..e55d71da0 100644 --- a/awesomerc.lua.in +++ b/awesomerc.lua.in @@ -383,6 +383,39 @@ client.connect_signal("manage", function (c, startup) awful.placement.no_offscreen(c) end end + + local titlebars_enabled = false + if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then + -- Widgets that are aligned to the left + local left_layout = wibox.layout.fixed.horizontal() + left_layout:add(awful.titlebar.widget.iconwidget(c)) + + -- Widgets that are aligned to the right + local right_layout = wibox.layout.fixed.horizontal() + right_layout:add(awful.titlebar.widget.floatingbutton(c)) + right_layout:add(awful.titlebar.widget.maximizedbutton(c)) + right_layout:add(awful.titlebar.widget.stickybutton(c)) + right_layout:add(awful.titlebar.widget.ontopbutton(c)) + right_layout:add(awful.titlebar.widget.closebutton(c)) + + -- The title goes in the middle + local title = awful.titlebar.widget.titlewidget(c) + title:buttons(awful.util.table.join( + awful.button({ }, 1, function() + client.focus = c + c:raise() + awful.mouse.client.move(c) + end) + )) + + -- Now bring it all together + local layout = wibox.layout.align.horizontal() + layout:set_left(left_layout) + layout:set_right(right_layout) + layout:set_middle(title) + + awful.titlebar(c):set_widget(layout) + end end) client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) diff --git a/lib/awful/init.lua.in b/lib/awful/init.lua.in index 62cf174e2..7f120b02d 100644 --- a/lib/awful/init.lua.in +++ b/lib/awful/init.lua.in @@ -29,6 +29,7 @@ return tooltip = require("awful.tooltip"); ewmh = require("awful.ewmh"); icccm = require("awful.icccm"); + titlebar = require("awful.titlebar"); } -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/awful/titlebar.lua.in b/lib/awful/titlebar.lua.in new file mode 100644 index 000000000..2b00bbe71 --- /dev/null +++ b/lib/awful/titlebar.lua.in @@ -0,0 +1,235 @@ +--------------------------------------------------------------------------- +-- @author Uli Schlachter +-- @copyright 2012 Uli Schlachter +-- @release @AWESOME_VERSION@ +--------------------------------------------------------------------------- + +local error = error +local type = type +local abutton = require("awful.button") +local aclient = require("awful.client") +local beautiful = require("beautiful") +local object = require("gears.object") +local drawable = require("wibox.drawable") +local base = require("wibox.widget.base") +local imagebox = require("wibox.widget.imagebox") +local textbox = require("wibox.widget.textbox") +local capi = { + client = client +} +local titlebar = { + widget = {} +} + +local all_titlebars = setmetatable({}, { __mode = 'k' }) + +-- Get a color for a titlebar, this tests many values from the array and the theme +local function get_color(name, c, args) + local suffix = "_normal" + if capi.client.focus == c then + suffix = "_focus" + end + local function get(array) + return array["titlebar_"..name..suffix] or array["titlebar_"..name] or array[name..suffix] or array[name] + end + return get(args) or get(beautiful) +end + +--- Get a client's titlebar +-- @class function +-- @param c The client for which a titlebar is wanted. +-- @param args An optional table with extra arguments for the titlebar. The +-- "size" is the height of the titlebar. Available "position" values are top, +-- left, right and bottom. Additionally, the foreground and background colors +-- can be configured via e.g. "bg_normal" and "bg_focus". +-- @name titlebar +local function new(c, args) + local args = args or {} + local position = args.position or "top" + local size = args.size or beautiful.get_font_height(args.font) * 1.5 + local d + + if position == "left" then + d = c:titlebar_left(size) + elseif position == "right" then + d = c:titlebar_right(size) + elseif position == "top" then + d = c:titlebar_top(size) + elseif position == "bottom" then + d = c:titlebar_bottom(size) + else + error("Invalid titlebar position '" .. position .. "'") + end + + -- Make sure that there is never more than one titlebar for any given client + local bars = all_titlebars[c] + if not bars then + bars = {} + all_titlebars[c] = bars + end + + local ret + if not bars[position] then + ret = drawable(d, nil, function() + -- On redraw, update the fg and bg colors of the titlebar + local args = bars[position].args + ret:set_bg(get_color("bg", c, args)) + ret:set_fg(get_color("fg", c, args)) + end) + + bars[position] = { + args = args, + drawable = ret + } + else + bars[position].args = args + ret = bars[position].drawable + end + + -- Make sure the titlebar is (re-)drawn + ret.draw() + + return ret +end + +--- Create a new titlewidget. A title widget displays the name of a client. +-- Please note that this returns a textbox and all of textbox' API is available. +-- This way, you can e.g. modify the font that is used. +-- @param c The client for which a titlewidget should be created. +-- @return The title widget. +function titlebar.widget.titlewidget(c) + local ret = textbox() + local function update() + ret:set_text(c.name or "") + end + c:connect_signal("property::name", update) + update() + + return ret +end + +--- Create a new icon widget. An icon widget displays the icon of a client. +-- Please note that this returns an imagebox and all of the imagebox' API is +-- available. This way, you can e.g. disallow resizes. +-- @param c The client for which an icon widget should be created. +-- @return The icon widget. +function titlebar.widget.iconwidget(c) + local ret = imagebox() + local function update() + ret:set_image(c.icon) + end + c:connect_signal("property::icon", update) + update() + + return ret +end + +--- Create a new button widget. A button widget displays an image and reacts to +-- mouse clicks. Please note that the caller has to make sure that this widget +-- gets redrawn when needed by calling the returned widget's update() function. +-- The selector function should return a value describing a state. If the value +-- is a boolean, either "active" or "inactive" are used. The actual image is +-- then found in the theme as "titlebar_[name]_button_[normal/focus]_[state]". +-- If that value does not exist, the focused state is ignored for the next try. +-- @param c The client for which a button is created. +-- @param name Name of the button, used for accessing the theme. +-- @param selector A function that selects the image that should be displayed. +-- @param action Function that is called when the button is clicked. +-- @return The widget +function titlebar.widget.button(c, name, selector, action) + local ret = imagebox() + local function update() + local img = selector(c) + if type(img) ~= "nil" then + -- Convert booleans automatically + if type(img) == "boolean" then + if img then + img = "active" + else + img = "inactive" + end + end + -- First try with a prefix based on the client's focus state + local prefix = "normal" + if capi.client.focus == c then + prefix = "focus" + end + if img ~= "" then + prefix = prefix .. "_" + end + local theme = beautiful["titlebar_" .. name .. "_button_" .. prefix .. img] + if not theme then + -- Then try again without that prefix if nothing was found + theme = beautiful["titlebar_" .. name .. "_button_" .. img] + end + if theme then + img = theme + end + end + ret:set_image(img) + end + if action then + ret:buttons(abutton({ }, 1, nil, function() action(c, selector(c)) end)) + end + + ret.update = update + update() + + -- We do magic based on whether a client is focused above, so we need to + -- connect to the corresponding signal here. + local function focus_func(o) + if o == c then update() end + end + capi.client.connect_signal("focus", focus_func) + capi.client.connect_signal("unfocus", focus_func) + + return ret +end + +--- Create a new float button for a client. +-- @param c The client for which the button is wanted. +function titlebar.widget.floatingbutton(c) + local widget = titlebar.widget.button(c, "floating", aclient.floating.get, aclient.floating.toggle) + c:connect_signal("property::floating", widget.update) + return widget +end + +--- Create a new maximize button for a client. +-- @param c The client for which the button is wanted. +function titlebar.widget.maximizedbutton(c) + local widget = titlebar.widget.button(c, "maximized", function(c) + return c.maximized_horizontal or c.maximized_vertical + end, function(c, state) + c.maximized_horizontal = not state + c.maximized_vertical = not state + end) + c:connect_signal("property::maximized_vertical", widget.update) + c:connect_signal("property::maximized_horizontal", widget.update) + return widget +end + +--- Create a new closing button for a client. +-- @param c The client for which the button is wanted. +function titlebar.widget.closebutton(c) + return titlebar.widget.button(c, "close", function() return "" end, function(c) c:kill() end) +end + +--- Create a new ontop button for a client. +-- @param c The client for which the button is wanted. +function titlebar.widget.ontopbutton(c) + local widget = titlebar.widget.button(c, "ontop", function(c) return c.ontop end, function(c, state) c.ontop = not state end) + c:connect_signal("property::ontop", widget.update) + return widget +end + +--- Create a new sticky button for a client. +-- @param c The client for which the button is wanted. +function titlebar.widget.stickybutton(c) + local widget = titlebar.widget.button(c, "sticky", function(c) return c.sticky end, function(c, state) c.sticky = not state end) + c:connect_signal("property::sticky", widget.update) + return widget +end + +return setmetatable(titlebar, { __call = function(_, ...) return new(...) end}) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/wibox/drawable.lua.in b/lib/wibox/drawable.lua.in index 8e7366cec..1bbec35a3 100644 --- a/lib/wibox/drawable.lua.in +++ b/lib/wibox/drawable.lua.in @@ -174,7 +174,7 @@ local function handle_motion(_drawable, x, y) _drawable._widgets_under_mouse = widgets end -function drawable.new(d, widget_arg) +function drawable.new(d, widget_arg, redraw_hook) local ret = object() ret.drawable = d ret.widget_arg = widget_arg or ret @@ -188,6 +188,7 @@ function drawable.new(d, widget_arg) -- Only redraw a drawable once, even when we get told to do so multiple times. ret._redraw_pending = false ret._do_redraw = function() + if redraw_hook then redraw_hook(ret) end ret._redraw_pending = false capi.awesome.disconnect_signal("refresh", ret._do_redraw) do_redraw(ret)