diff --git a/docs/03-declarative-layout.md b/docs/03-declarative-layout.md index 9903444f..0ba6e931 100644 --- a/docs/03-declarative-layout.md +++ b/docs/03-declarative-layout.md @@ -1,7 +1,6 @@ -# The declarative layout system +# The AwesomeWM widget system -The declarative layout system provides an alternative to the imperative system. -It is inspired by the one used by Awesome 3.2-3.4 and the Qt QML style. +This document explains how to define, place and manage widgets. ## The default widgets @@ -29,7 +28,127 @@ configurable rules. @DOC_layout_WIDGET_LIST@ -## Placing widgets +### The different type of widget boxes (Wibox) + +The Awesome API uses the word "wibox" (widget box) to describe an area of the +screen filled with widgets. There are many subvariants of wiboxes with +specialized roles such as widget bars or tooltips. All variants mostly share the +same characteristics, but add some extra features to make those specialized +widget boxes easier to work with. + +@DOC_awful_popup_wiboxtypes_EXAMPLE@ + +The normal `wibox` is the base class for each of these types. It is extremely +flexible and allows to place just about anything on the screen. However it +requires a lot of repetitive boilerplate code to use directly. For example, +the user needs to compute the optimal size by hand or use `awful.placement`. + +The `awful.wibar` specialization allows to attach a `wibox` to a screen edge +and prevents clients from using this area when tiled. + +The `awful.popup` allows to easily place widgets on the screen. It automatically +resizes itself to fit the optimal widget size. It also has helper properties +and methods to make it easy to place it on the screen. It supports absolute +positioning, relative positioning, and manual positioning. + +The `awful.tooltip` is a very simple `wibox` that allows to display text next +to an object such as the mouse. + +Finally, the `awful.titlebar`, while not technically a real `wibox`, acts +exactly the same way and allows to attach widgets on each side of clients. + +## The different syntaxes to initiate widgets + +Awesome provides 2 totally different API access styles to manage widgets. Both +suit different use cases. Under the hood, both produce the +exact same code. Consider the declarative API to be compiled into the +imperative syntax when loaded. Also note that in contrast to technologies such +as QML, it is interpreted only once and isn't automatically updated when +values change. + +The **imperative** widget initialization is similar to QtWidgets, GTK and Win32. +You create the object, then set the property and add the widget as a child to +another already declared widget. It is quite simple to use but very verbose +and full of boilerplate code. The imperative API also offers properties both +with accessors or directly. It is useful when creating highly dynamic layouts +where widgets are added and removed over the course of their lifecycle. + +The **declarative** syntax resembles HTML style code +written in JSON or YAML. The widget instances are created automatically and +the hierarchy is related to the table nesting (indentation). It is preferred +when creating static layouts that won't change over the course of their +lifecycle. + +Here is the same code written in both the imperative and declarative style + +**Imperative with accessors** + +Code: + + local bg = wibox.container.background() + bg:set_bg("#ff0000") + + local tb1 = wibox.widget.textbox() + local tb2 = wibox.widget.textbox("bar") + + tb1:set_text("foo") + tb2:set_text("bar") + + local l = wibox.layout.fixed.vertical() + l:add(tb1) + l:add(tb2) + + bg:set_widget(l) + +**Imperative with properties** + +Code: + + local bg = wibox.container.background() + bg.bg = "#ff0000" + + local tb1 = wibox.widget.textbox("foo") + local tb2 = wibox.widget.textbox("bar") + + tb1.text = "foo" + tb2.text = "bar" + + local l = wibox.layout.fixed.vertical() + l:add(tb1) + l:add(tb2) + + bg.widget = l + +**Declarative** + +Code: + + local bg = wibox.widget { + { + { + text = "foo", + widget = wibox.widget.textbox + }, + { + text = "bar", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + bg = "#ff0000", + widget = wibox.container.background + } + + +The Awesome documentation mostly uses the declarative style for consistency, +but both are **always** available. Note that each style can be mixed with other +styles, but this creates very confusing code and should be avoided. + +## Creating and placing widgets using the declarative style + +The examples below explain in detail how to use the declarative layout system. +The imperative system is quite self explanatory and the respective widget API +documentation should be enough for most. ### A simple layout diff --git a/lib/awful/init.lua b/lib/awful/init.lua index ed2fb535..2a416879 100644 --- a/lib/awful/init.lua +++ b/lib/awful/init.lua @@ -59,6 +59,7 @@ return ewmh = require("awful.ewmh"); titlebar = require("awful.titlebar"); rules = require("awful.rules"); + popup = require("awful.popup"); spawn = spawn; } diff --git a/lib/awful/popup.lua b/lib/awful/popup.lua new file mode 100644 index 00000000..7dc4e59f --- /dev/null +++ b/lib/awful/popup.lua @@ -0,0 +1,467 @@ +--------------------------------------------------------------------------- +--- An auto-resized, free floating or modal wibox built around a widget. +-- +-- This type of widget box (wibox) is auto closed when being clicked on and is +-- automatically resized to the size of its main widget. +-- +-- Note that the widget itself should have a finite size. If something like a +-- `wibox.layout.flex` is used, then the size would be unlimited and an error +-- will be printed. The `wibox.layout.fixed`, `wibox.container.constraint`, +-- `forced_width` and `forced_height` are recommended. +-- +--@DOC_awful_popup_simple_EXAMPLE@ +-- +-- Here is an example of how to create an alt-tab like dialog by leveraging +-- the `awful.widget.tasklist`. +-- +--@DOC_awful_popup_alttab_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee +-- @copyright 2016 Emmanuel Lepage Vallee +-- @classmod awful.popup +--------------------------------------------------------------------------- +local wibox = require( "wibox" ) +local util = require( "awful.util" ) +local placement = require( "awful.placement" ) +local xresources= require("beautiful.xresources") +local timer = require( "gears.timer" ) +local capi = {mouse = mouse} + + +local module = {} + +local main_widget = {} + +-- Get the optimal direction for the wibox +-- This (try to) avoid going offscreen +local function set_position(self) + -- First, if there is size to be applied, do it + if self._private.next_width then + self.width = self._private.next_width + self._private.next_width = nil + end + + if self._private.next_height then + self.height = self._private.next_height + self._private.next_height = nil + end + + local pf = self._private.placement + + if pf == false then return end + + if pf then + pf(self, {bounding_rect = self.screen.geometry}) + return + end + + local geo = self._private.widget_geo + + if not geo then return end + + local _, pos_name, anchor_name = placement.next_to(self, { + preferred_positions = self._private.preferred_directions, + geometry = geo, + preferred_anchors = self._private.preferred_anchors, + offset = self._private.offset or { x = 0, y = 0}, + }) + + if pos_name ~= self._private.current_position then + local old = self._private.current_position + self._private.current_position = pos_name + self:emit_signal("property::current_position", pos_name, old) + end + + if anchor_name ~= self._private.current_anchor then + local old = self._private.current_anchor + self._private.current_anchor = anchor_name + self:emit_signal("property::current_anchor", anchor_name, old) + end +end + +-- Set the wibox size taking into consideration the limits +local function apply_size(self, width, height, set_pos) + local prev_geo = self:geometry() + + width = math.max(self._private.minimum_width or 1, math.ceil(width or 1)) + height = math.max(self._private.minimum_height or 1, math.ceil(height or 1)) + + if self._private.maximum_width then + width = math.min(self._private.maximum_width, width) + end + + if self._private.maximum_height then + height = math.min(self._private.maximum_height, height) + end + + self._private.next_width, self._private.next_height = width, height + + if set_pos or width ~= prev_geo.width or height ~= prev_geo.height then + set_position(self) + end +end + +-- Layout this widget +function main_widget:layout(context, width, height) + if self._private.widget then + local w, h = wibox.widget.base.fit_widget( + self, + context, + self._private.widget, + self._wb._private.maximum_width or 9999, + self._wb._private.maximum_height or 9999 + ) + timer.delayed_call(function() + apply_size(self._wb, w, h, true) + end) + return { wibox.widget.base.place_widget_at(self._private.widget, 0, 0, width, height) } + end +end + +-- Set the widget that is drawn on top of the background +function main_widget:set_widget(widget) + if widget then + wibox.widget.base.check_widget(widget) + end + self._private.widget = widget + self:emit_signal("widget::layout_changed") +end + +function main_widget:get_widget() + return self._private.widget +end + +function main_widget:get_children_by_id(name) + return self._wb:get_children_by_id(name) +end + +local popup = {} + +--- Set the preferred popup position relative to its parent. +-- +-- This allows, for example, to have a submenu that goes on the right of the +-- parent menu. If there is no space on the right, it tries on the left and so +-- on. +-- +-- Valid directions are: +-- +-- * left +-- * right +-- * top +-- * bottom +-- +-- The basic use case for this method is to give it a parent wibox: +-- +-- @DOC_awful_popup_position1_EXAMPLE@ +-- +-- As demonstrated by this second example, it is also possible to use a widget +-- as a parent object: +-- +-- @DOC_awful_popup_position2_EXAMPLE@ +-- +-- @property preferred_positions +-- @tparam table|string preferred_positions A position name or an ordered +-- table of positions +-- @see awful.placement.next_to +-- @see awful.popup.preferred_anchors + +function popup:set_preferred_positions(pref_pos) + self._private.preferred_directions = pref_pos + set_position(self) +end + +--- Set the preferred popup anchors relative to the parent. +-- +-- The possible values are: +-- +-- * front +-- * middle +-- * back +-- +-- For details information, see the `awful.placement.next_to` documentation. +-- +-- In this example, it is possible to see the effect of having a fallback +-- preferred anchors when the popup would otherwise not fit: +-- +-- @DOC_awful_popup_anchors_EXAMPLE@ +-- +-- @property preferred_anchors +-- @tparam table|string preferred_anchors Either a single anchor name or a table +-- ordered by priority. +-- @see awful.placement.next_to +-- @see awful.popup.preferred_positions + +function popup:set_preferred_anchors(pref_anchors) + self._private.preferred_anchors = pref_anchors + set_position(self) +end + +--- The current position relative to the parent object. +-- +-- If there is a parent object (widget, wibox, wibar, client or the mouse), then +-- this property returns the current position. This is determined using +-- `preferred_positions`. It is usually the preferred position, but when there +-- isn't enough space, it can also be one of the fallback. +-- +-- @property current_position +-- @tparam string current_position Either "left", "right", "top" or "bottom" + +function popup:get_current_position() + return self._private.current_position +end + +--- Get the current anchor relative to the parent object. +-- +-- If there is a parent object (widget, wibox, wibar, client or the mouse), then +-- this property returns the current anchor. The anchor is the "side" of the +-- parent object on which the popup is based on. It will "grow" in the +-- opposite direction from the anchor. +-- +-- @property current_anchor +-- @tparam string current_anchor Either "front", "middle", "back" + +function popup:get_current_anchor() + return self._private.current_anchor +end + +--- Move the wibox to a position relative to `geo`. +-- This will try to avoid overlapping the source wibox and auto-detect the right +-- direction to avoid going off-screen. +-- +-- @param[opt=mouse] obj An object such as a wibox, client or a table entry +-- returned by `wibox:find_widgets()`. +-- @see awful.placement.next_to +-- @see awful.popup.preferred_positions +-- @see awful.popup.preferred_anchors +-- @treturn table The new geometry +function popup:move_next_to(obj) + if self._private.is_relative == false then return end + + self._private.widget_geo = obj + + obj = obj or capi.mouse + + if obj._apply_size_now then + obj:_apply_size_now(false) + end + + self.visible = true + + self:_apply_size_now(true) + + self._private.widget_geo = nil +end + +--- Bind the popup to a widget button press. +-- +-- @tparam widget widget The widget +-- @tparam[opt=1] number button The button index +function popup:bind_to_widget(widget, button) + if not self._private.button_for_widget then + self._private.button_for_widget = {} + end + + self._private.button_for_widget[widget] = button or 1 + widget:connect_signal("button::press", self._private.show_fct) +end + +--- Unbind the popup from a widget button. +-- @tparam widget widget The widget +function popup:unbind_to_widget(widget) + widget:disconnect_signal("button::press", self._private.show_fct) +end + +--- Hide the popup when right clicked. +-- +-- @property hide_on_right_click +-- @tparam[opt=false] boolean hide_on_right_click + +function popup:set_hide_on_right_click(value) + self[value and "connect_signal" or "disconnect_signal"]( + self, "button::press", self._private.hide_fct + ) +end + +--- The popup minimum width. +-- @property minimum_width +-- @tparam[opt=1] number The minimum width + +--- The popup minimum height. +-- @property minimum_height +-- @tparam[opt=1] number The minimum height + +--- The popup minimum width. +-- @property maxmimum_width +-- @tparam[opt=1] number The maxmimum width + +--- The popup maximum height. +-- @property maximum_height +-- @tparam[opt=1] number The maximum height + +for _, orientation in ipairs {"_width", "_height"} do + for _, limit in ipairs {"minimum", "maximum"} do + popup["set_"..limit..orientation] = function(self, value) + self._private[limit..orientation] = value + self._private.container:emit_signal("widget::layout_changed") + end + end +end + +--- The distance between the popup and its parent (if any). +-- +-- Here is an example of 5 popups stacked one below the other with an y axis +-- offset (spacing). +-- +-- @DOC_awful_popup_position3_EXAMPLE@ +-- @property offset +-- @tparam table|number offset An integer value or a `{x=, y=}` table. +-- @tparam[opt=offset] number offset.x The horizontal distance. +-- @tparam[opt=offset] number offset.y The vertical distance. + +function popup:set_offset(offset) + + if type(offset) == "number" then + offset = { + x = offset or 0, + y = offset or 0, + } + end + + local oldoff = self._private.offset or {x=0, y=0} + + if oldoff.x == offset.x and oldoff.y == offset.y then return end + + offset.x, offset.y = offset.x or oldoff.x or 0, offset.y or oldoff.y or 0 + + self._private.offset = offset + + self:_apply_size_now(false) +end + +--- Set the placement function. +-- @tparam[opt=next_to] function|string|boolean The placement function or name +-- (or false to disable placement) +-- @property placement +-- @param function + +function popup:set_placement(f) + if type(f) == "string" then + f = placement[f] + end + + self._private.placement = f + self:_apply_size_now(false) +end + +-- For the tests and the race condition when 2 popups are placed next to each +-- other. +function popup:_apply_size_now(skip_set) + if not self.widget then return end + + local w, h = wibox.widget.base.fit_widget( + self.widget, + {dpi= self.screen.dpi or xresources.get_dpi()}, + self.widget, + self._private.maximum_width or 9999, + self._private.maximum_height or 9999 + ) + + -- It is important to do it for the obscure reason that calling `w:geometry()` + -- is actually mutating the state due to quantum determinism thanks to XCB + -- async nature... It is only true the very first time `w:geometry()` is + -- called + self.width = math.max(1, math.ceil(w or 1)) + self.height = math.max(1, math.ceil(h or 1)) + + apply_size(self, w, h, skip_set ~= false) +end + +function popup:set_widget(wid) + self._private.widget = wid + self._private.container:set_widget(wid) +end + +function popup:get_widget() + return self._private.widget +end + +--- Create a new popup build around a passed in widget. +-- @tparam[opt=nil] table args +--@DOC_wibox_constructor_COMMON@ +-- @tparam function args.placement The `awful.placement` function +-- @tparam string|table args.preferred_positions +-- @tparam string|table args.preferred_anchors +-- @tparam table|number args.offset The X and Y offset compared to the parent object +-- @tparam boolean args.hide_on_right_click Whether or not to hide the popup on +-- right clicks. +-- @function awful.popup +local function create_popup(_, args) + assert(args) + + -- Temporarily remove the widget + local original_widget = args.widget + args.widget = nil + + assert(original_widget, "The `awful.popup` requires a `widget` constructor argument") + + local child_widget = wibox.widget.base.make_widget_from_value(original_widget) + + local ii = wibox.widget.base.make_widget(child_widget, "awful.popup", { + enable_properties = true + }) + + util.table.crush(ii, main_widget, true) + + -- Create a wibox to host the widget + local w = wibox(args or {}) + + rawset(w, "_private", { + container = ii, + preferred_directions = { "right", "left", "top", "bottom" }, + preferred_anchors = { "back", "front", "middle" }, + widget = child_widget + }) + + util.table.crush(w, popup) + + ii:set_widget(child_widget) + + -- Create the signal handlers + function w._private.show_fct(wdg, _, _, button, _, geo) + if button == w._private.button_for_widget[wdg] then + w:move_next_to(geo) + end + end + function w._private.hide_fct() + w.visible = false + end + + -- Restore + args.widget = original_widget + + -- Cross-link the wibox and widget + ii._wb = w + wibox.set_widget(w, ii) + + --WARNING The order is important + -- First, apply the limits to avoid a flicker with large width or height + -- when set_position is called before the limits + for _,v in ipairs{"minimum_width", "minimum_height", "maximum_height", + "maximum_width", "offset", "placement","preferred_positions", + "preferred_anchors", "hide_on_right_click"} do + if args[v] ~= nil then + w["set_"..v](w, args[v]) + end + end + + -- Default to visible + if args.visible ~= false then + w.visible = true + end + + return w +end + +--@DOC_wibox_COMMON@ + +return setmetatable(module, {__call = create_popup}) diff --git a/tests/examples/awful/popup/alttab.lua b/tests/examples/awful/popup/alttab.lua new file mode 100644 index 00000000..d56c1741 --- /dev/null +++ b/tests/examples/awful/popup/alttab.lua @@ -0,0 +1,59 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { --DOC_HIDE --DOC_NO_USAGE + popup = require("awful.popup"), --DOC_HIDE + placement = require("awful.placement"), --DOC_HIDE + widget = {clienticon =require("awful.widget.clienticon"), --DOC_HIDE + tasklist = require("awful.widget.tasklist")} --DOC_HIDE +} --DOC_HIDE +local gears = { shape = require("gears.shape") } --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + +for _=1, 6 do --DOC_HIDE + local c = client.gen_fake {x = 80, y = 55, width=75, height=50} --DOC_HIDE + c.icon = beautiful.awesome_icon --DOC_HIDE + c.minimized = true --DOC_HIDE +end --DOC_HIDE + +local tasklist_buttons = nil --DOC_HIDE + + awful.popup { + widget = awful.widget.tasklist { + screen = screen[1], + filter = awful.widget.tasklist.filter.allscreen, + buttons = tasklist_buttons, + style = { + shape = gears.shape.rounded_rect, + }, + layout = { + spacing = 5, + forced_num_rows = 2, + layout = wibox.layout.grid.horizontal + }, + + widget_template = { + { + { + id = "clienticon", + widget = awful.widget.clienticon, + }, + margins = 4, + widget = wibox.container.margin, + }, + id = "background_role", + forced_width = 48, + forced_height = 48, + widget = wibox.container.background, + create_callback = function(self, c, index, objects) --luacheck: no unused + self:get_children_by_id("clienticon")[1].client = c + end, + }, + }, + border_color = "#777777", + border_width = 2, + ontop = true, + placement = awful.placement.centered, + shape = gears.shape.rounded_rect + } + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/anchors.lua b/tests/examples/awful/popup/anchors.lua new file mode 100644 index 00000000..0bbc89a7 --- /dev/null +++ b/tests/examples/awful/popup/anchors.lua @@ -0,0 +1,70 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { --DOC_HIDE --DOC_NO_USAGE + popup = require("awful.popup"), --DOC_HIDE + placement = require("awful.placement"), --DOC_HIDE + widget = {clienticon =require("awful.widget.clienticon"), --DOC_HIDE + tasklist = require("awful.widget.tasklist")} --DOC_HIDE +} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE + +screen[1]._resize {width = 320, height = 240} --DOC_HIDE +screen._add_screen {x = 330, y = 0, width = 320, height = 240} --DOC_HIDE + +local p = awful.popup { --DOC_HIDE + widget = wibox.widget { --DOC_HIDE + text = "Parent wibox", --DOC_HIDE + forced_width = 100, --DOC_HIDE + forced_height = 50, --DOC_HIDE + widget = wibox.widget.textbox --DOC_HIDE + }, --DOC_HIDE + border_color = "#777777", --DOC_HIDE + border_width = 2, --DOC_HIDE + ontop = true, --DOC_HIDE + screen = screen[1], --DOC_HIDE + placement = awful.placement.top, --DOC_HIDE +} --DOC_HIDE +p:_apply_size_now() --DOC_HIDE + + local p2 = awful.popup { + widget = wibox.widget { + text = "A popup", + forced_height = 100, + widget = wibox.widget.textbox + }, + border_color = "#777777", + border_width = 2, + preferred_positions = "right", + preferred_anchors = {"front", "back"}, + } + p2:move_next_to(p) --DOC_HIDE + +local p3 = awful.popup { --DOC_HIDE + widget = wibox.widget { --DOC_HIDE + text = "Parent wibox2", --DOC_HIDE + forced_width = 100, --DOC_HIDE + forced_height = 50, --DOC_HIDE + widget = wibox.widget.textbox --DOC_HIDE + }, --DOC_HIDE + border_color = "#777777", --DOC_HIDE + border_width = 2, --DOC_HIDE + ontop = true, --DOC_HIDE + screen = screen[2], --DOC_HIDE + placement = awful.placement.bottom, --DOC_HIDE +} --DOC_HIDE +p3:_apply_size_now() --DOC_HIDE + + local p4 = awful.popup { + widget = wibox.widget { + text = "A popup2", + forced_height = 100, + widget = wibox.widget.textbox + }, + border_color = "#777777", + border_width = 2, + preferred_positions = "right", + screen = screen[2], --DOC_HIDE + preferred_anchors = {"front", "back"}, + } + p4:move_next_to(p3) --DOC_HIDE + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/position1.lua b/tests/examples/awful/popup/position1.lua new file mode 100644 index 00000000..0d4bca84 --- /dev/null +++ b/tests/examples/awful/popup/position1.lua @@ -0,0 +1,38 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { --DOC_HIDE --DOC_NO_USAGE + popup = require("awful.popup"), --DOC_HIDE + placement = require("awful.placement"), --DOC_HIDE + widget = {clienticon =require("awful.widget.clienticon"), --DOC_HIDE + tasklist = require("awful.widget.tasklist")} --DOC_HIDE +} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE + +local p = awful.popup { --DOC_HIDE + widget = wibox.widget { --DOC_HIDE + text = "Parent wibox", --DOC_HIDE + forced_width = 100, --DOC_HIDE + forced_height = 100, --DOC_HIDE + widget = wibox.widget.textbox --DOC_HIDE + }, --DOC_HIDE + border_color = "#777777", --DOC_HIDE + border_width = 2, --DOC_HIDE + ontop = true, --DOC_HIDE + placement = awful.placement.centered, --DOC_HIDE +} --DOC_HIDE +p:_apply_size_now() --DOC_HIDE + + for _, v in ipairs {"left", "right", "bottom", "top"} do + local p2 = awful.popup { + widget = wibox.widget { + text = "On the "..v, + widget = wibox.widget.textbox + }, + border_color = "#777777", + border_width = 2, + preferred_positions = v, + ontop = true, + } + p2:move_next_to(p) + end + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/position2.lua b/tests/examples/awful/popup/position2.lua new file mode 100644 index 00000000..57de2deb --- /dev/null +++ b/tests/examples/awful/popup/position2.lua @@ -0,0 +1,61 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { --DOC_HIDE --DOC_NO_USAGE + popup = require("awful.popup"), --DOC_HIDE + placement = require("awful.placement"), --DOC_HIDE + widget = {clienticon =require("awful.widget.clienticon"), --DOC_HIDE + tasklist = require("awful.widget.tasklist")} --DOC_HIDE +} --DOC_HIDE +local gears = { shape = require("gears.shape"), timer=require("gears.timer") } --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + +local p = awful.popup { --DOC_HIDE + widget = wibox.widget { --DOC_HIDE + { text = "Item", widget = wibox.widget.textbox }, --DOC_HIDE + { text = "Item", widget = wibox.widget.textbox }, --DOC_HIDE + { text = "Item", widget = wibox.widget.textbox }, --DOC_HIDE + { --DOC_HIDE + { --DOC_HIDE + text = "Selected", --DOC_HIDE + widget = wibox.widget.textbox --DOC_HIDE + }, --DOC_HIDE + bg = beautiful.bg_highlight, --DOC_HIDE + widget = wibox.container.background --DOC_HIDE + }, --DOC_HIDE + { text = "Item", widget = wibox.widget.textbox }, --DOC_HIDE + { text = "Item", widget = wibox.widget.textbox }, --DOC_HIDE + forced_width = 100, --DOC_HIDE + widget = wibox.layout.fixed.vertical --DOC_HIDE + }, --DOC_HIDE + border_color = "#ff0000", --DOC_HIDE + border_width = 2, --DOC_HIDE + placement = awful.placement.centered, --DOC_HIDE +} --DOC_HIDE +p:_apply_size_now() --DOC_HIDE +awesome.emit_signal("refresh") --DOC_HIDE +p._drawable._do_redraw() --DOC_HIDE + +--DOC_HIDE Necessary as the widgets are drawn later +gears.timer.delayed_call(function() --DOC_HIDE +-- Get the 4th textbox --DOC_HIDE +local list = p:find_widgets(30, 40) --DOC_HIDE +mouse.coords {x= 120, y=125} --DOC_HIDE +mouse.push_history() --DOC_HIDE +local textboxinstance = list[#list] --DOC_HIDE + + for _, v in ipairs {"left", "right"} do + local p2 = awful.popup { + widget = wibox.widget { + text = "On the "..v, + forced_height = 100, + widget = wibox.widget.textbox + }, + border_color = "#0000ff", + preferred_positions = v, + border_width = 2, + } + p2:move_next_to(textboxinstance, v) + end + end) --DOC_HIDE + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/position3.lua b/tests/examples/awful/popup/position3.lua new file mode 100644 index 00000000..0f9597be --- /dev/null +++ b/tests/examples/awful/popup/position3.lua @@ -0,0 +1,34 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { --DOC_HIDE --DOC_NO_USAGE + popup = require("awful.popup"), --DOC_HIDE + placement = require("awful.placement"), --DOC_HIDE + widget = {clienticon =require("awful.widget.clienticon"), --DOC_HIDE + tasklist = require("awful.widget.tasklist")} --DOC_HIDE +} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local previous = nil + + for i=1, 5 do + local p2 = awful.popup { + widget = wibox.widget { + text = "Hello world! "..i.." aaaa.", + widget = wibox.widget.textbox + }, + border_color = beautiful.border_color, + preferred_positions = "bottom", + border_width = 2, + preferred_anchors = "back", + placement = (not previous) and awful.placement.top or nil, + offset = { + y = 10, + }, + } + p2:_apply_size_now() --DOC_HIDE + p2:move_next_to(previous) + previous = p2 + previous:_apply_size_now() --DOC_HIDE + end + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/simple.lua b/tests/examples/awful/popup/simple.lua new file mode 100644 index 00000000..24653680 --- /dev/null +++ b/tests/examples/awful/popup/simple.lua @@ -0,0 +1,42 @@ +--DOC_GEN_IMAGE --DOC_HIDE +local awful = { placement = require("awful.placement"), --DOC_HIDE + popup = require("awful.popup") } --DOC_HIDE --DOC_NO_USAGE +local gears = { shape = require("gears.shape") } --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE + + awful.popup { + widget = { + { + { + text = "foobar", + widget = wibox.widget.textbox + }, + { + { + text = "foobar", + widget = wibox.widget.textbox + }, + bg = "#ff00ff", + clip = true, + shape = gears.shape.rounded_bar, + widget = wibox.widget.background + }, + { + value = 0.5, + forced_height = 30, + forced_width = 100, + widget = wibox.widget.progressbar + }, + layout = wibox.layout.fixed.vertical, + }, + margins = 10, + widget = wibox.container.margin + }, + border_color = "#00ff00", + border_width = 5, + placement = awful.placement.top_left, + shape = gears.shape.rounded_rect, + visible = true, + } + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/popup/wiboxtypes.lua b/tests/examples/awful/popup/wiboxtypes.lua new file mode 100644 index 00000000..5c673372 --- /dev/null +++ b/tests/examples/awful/popup/wiboxtypes.lua @@ -0,0 +1,247 @@ +--DOC_GEN_IMAGE +--DOC_HIDE_ALL +--DOC_NO_USAGE +local awful = require("awful") +local gears = require("gears") +local wibox = require("wibox") +local beautiful = require("beautiful") --DOC_HIDE + +screen[1]._resize {width = 640, height = 480} + +-- This example is used to show the various type of wibox awesome provides +-- and mimic the default config look + +local c = client.gen_fake {hide_first=true} + +c:geometry { + x = 50, + y = 350, + height = 100, + width = 150, +} +c._old_geo = {c:geometry()} +c:set_label("A client") + +local wb = awful.wibar { + position = "top", +} + +-- Create the same number of tags as the default config +awful.tag({ "1", "2", "3", "4", "5", "6", "7", "8", "9" }, screen[1], awful.layout.layouts[1]) + +-- Only bother with widgets that are visible by default +local mykeyboardlayout = awful.widget.keyboardlayout() +local mytextclock = wibox.widget.textclock() +local mylayoutbox = awful.widget.layoutbox(screen[1]) +local mytaglist = awful.widget.taglist(screen[1], awful.widget.taglist.filter.all, {}) +local mytasklist = awful.widget.tasklist(screen[1], awful.widget.tasklist.filter.currenttags, {}) + +wb:setup { + layout = wibox.layout.align.horizontal, + { -- Left widgets + layout = wibox.layout.fixed.horizontal, + awful.titlebar.widget.iconwidget(c), --looks close enough + mytaglist, + }, + mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + mykeyboardlayout, + mytextclock, + mylayoutbox, + }, +} + +-- The popup +awful.popup { + widget = wibox.widget { + --TODO use the layoutlist for this example + awful.widget.taglist { + filter = awful.widget.taglist.filter.all, + screen = 1, + base_layout = wibox.widget { + spacing = 5, + forced_num_cols = 5, + layout = wibox.layout.grid.vertical, + }, + widget_template = { + { +--TODO use the layoutlist for this example +-- { +-- id = 'icon_role', +-- forced_height = 22, +-- forced_width = 22, +-- widget = wibox.widget.imagebox, +-- }, + { + id = "text_role", + forced_height = 22, + forced_width = 22, + widget = wibox.widget.textbox + }, + margins = 4, + widget = wibox.container.margin, + }, + id = 'background_role', + forced_width = 24, + forced_height = 24, + shape = gears.shape.rounded_rect, + widget = wibox.container.background, + }, + }, + margins = 4, + widget = wibox.container.margin, + }, + border_color = beautiful.border_color, + border_width = beautiful.border_width, + placement = awful.placement.centered, + ontop = true, + shape = gears.shape.rounded_rect +} + +-- poor copy of awful.widget.calendar_widget until I fix its API to be less +-- insane. +local p10 = awful.popup { + widget = { + wibox.widget.calendar.month(os.date('*t')), + top = 30, + margins = 10, + layout = wibox.container.margin + }, + preferred_anchors = "middle", + border_width = 2, + border_color = beautiful.border_color, + hide_on_right_click = true, + placement = function(d) return awful.placement.top_right(d, { + honor_workarea = true, + }) end, + shape = gears.shape.infobubble, +} + +awesome.emit_signal("refresh") +p10:bind_to_widget(mytextclock) + +-- The titlebar + +local top_titlebar = awful.titlebar(c, { + height = 20, + bg_normal = "#ff0000", +}) + +top_titlebar : setup { + { -- Left + awful.titlebar.widget.iconwidget(c), + layout = wibox.layout.fixed.horizontal + }, + { -- Middle + { -- Title + align = "center", + widget = awful.titlebar.widget.titlewidget(c) + }, + layout = wibox.layout.flex.horizontal + }, + { -- Right + awful.titlebar.widget.floatingbutton (c), + awful.titlebar.widget.maximizedbutton(c), + awful.titlebar.widget.stickybutton (c), + awful.titlebar.widget.ontopbutton (c), + awful.titlebar.widget.closebutton (c), + layout = wibox.layout.fixed.horizontal() + }, + layout = wibox.layout.align.horizontal +} + +-- Normal wiboxes + +wibox { + width = 50, + height = 50, + shape = gears.shape.octogon, + color = "#0000ff", + x = 570, + y = 410, + border_width = 2, + border_color = beautiful.border_color, +} + +-- The tooltip + +mouse.coords{x=50, y= 100} +mouse.push_history() + +local tt = awful.tooltip { + text = "A tooltip!", + visible = true, +} +tt.bg = beautiful.bg_normal + +-- Extra information overlay + +local overlay_w = wibox { + bg = "#00000000", + visible = true, + ontop = true, +} + +awful.placement.maximize(overlay_w) + +local canvas = wibox.layout.manual() +canvas.forced_height = 480 +canvas.forced_width = 640 +overlay_w:set_widget(canvas) + +local function create_info(text, x, y, width, height) + canvas:add_at(wibox.widget { + { + { + text = text, + align = "center", + ellipsize = "none", + widget = wibox.widget.textbox + }, + margins = 10, + widget = wibox.container.margin + }, + forced_width = width, + forced_height = height, + shape = gears.shape.rectangle, + shape_border_width = 1, + shape_border_color = beautiful.border_color, + bg = "#ffff0055", + widget = wibox.container.background + }, {x = x, y = y}) +end + +local function create_line(x1, y1, x2, y2) + return canvas:add_at(wibox.widget { + fit = function() + return x2-x1+6, y2-y1+6 + end, + draw = function(_, _, cr) + cr:set_source_rgb(0,0,0) + cr:set_line_width(1) + cr:arc(1.5, 1.5, 1.5, 0, math.pi*2) + cr:arc(x2-x1+1.5, y2-y1+1.5, 1.5, 0, math.pi*2) + cr:fill() + cr:move_to(1.5,1.5) + cr:line_to(x2-x1+1.5, y2-y1+1.5) + cr:stroke() + end, + layout = wibox.widget.base.make_widget, + }, {x=x1, y=y1}) +end + +create_info("awful.wibar", 200, 50, 100, 30) +create_info("awful.titlebar", 250, 350, 100, 30) +create_info("awful.tooltip", 30, 130, 100, 30) +create_info("awful.popup", 450, 240, 100, 30) +create_info("Standard `wibox1`", 420, 420, 130, 30) + +create_line(250, 10, 250, 55) +create_line(75, 100, 75, 135) +create_line(545, 432, 575, 432) +create_line(500, 165, 500, 245) +create_line(390, 250, 450, 250) +create_line(190, 365, 255, 365) + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80