diff --git a/docs/03-declarative-layout.md b/docs/03-declarative-layout.md index 0ba6e931f..c27ba19b6 100644 --- a/docs/03-declarative-layout.md +++ b/docs/03-declarative-layout.md @@ -28,6 +28,15 @@ configurable rules. @DOC_layout_WIDGET_LIST@ +### Other + +Notifications also have their own widgets. + + + +More information about the notification widgets can be found on the +`naughty.notification` documentation page. + ### The different type of widget boxes (Wibox) The Awesome API uses the word "wibox" (widget box) to describe an area of the @@ -54,6 +63,9 @@ 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. +The `naughty.layout.box` allows to provide custom widgets to use within the +notifications. + 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. @@ -424,3 +436,27 @@ Code: s.mywibox : setup (three_circle) +### Instantiation rules + +Whenever it can, Awesome tries to be asynchronous. This can take various form +depending on the situation. For example, the `connect_signal` method allows to +execute code when an event arrives. `awful.screen.connect_for_each_screen` also +allows to instantiate various elements when a new screen is added. In the later +case, it is why some widgets are added as properties to other objects instead of +being global variables like in previous versions of Awesome. + +However, there is a case where this isn't enough and another abstract widget has +to be used. This concept is called the `widget_template` and is an optional +property of many widgets such as the `awful.widget.taglist`, +`awful.widget.tasklist` and `naughty.layout.box`. These templates are a +**table** using the exact same syntax as the declarative widgets, but without +the `wibox.widget` prefix in front of the curly braces. These templates +represents future widgets that will be created by their parent widget. This is +necessary for three reasons: + + * The widget must create many instances of the template at different points in + time. + * The widget data is only partially available and other fields must be set + at a later time (by the parent widget). + * The code is highly redundant and some of the logic is delegated to the parent + widget to simplify everything. diff --git a/docs/config.ld b/docs/config.ld index 64bf0cb2c..854a223e9 100644 --- a/docs/config.ld +++ b/docs/config.ld @@ -149,6 +149,10 @@ file = { -- Ignore some parts of the widget library '../lib/awful/widget/init.lua', '../lib/naughty/layout/init.lua', + '../lib/naughty/widget/init.lua', + '../lib/naughty/container/init.lua', + '../lib/naughty/list/init.lua', + '../lib/naughty/widget/_default.lua', -- Deprecated classes for one years or more don't deserve entries -- in the index diff --git a/lib/naughty/action.lua b/lib/naughty/action.lua index 28e0de033..abc24c4da 100644 --- a/lib/naughty/action.lua +++ b/lib/naughty/action.lua @@ -6,7 +6,7 @@ -- -- @author Emmanuel Lepage Vallee <elv1313@gmail.com> -- @copyright 2019 Emmanuel Lepage Vallee --- @classmod naughty.action +-- @coreclassmod naughty.action --------------------------------------------------------------------------- local gtable = require("gears.table" ) local gobject = require("gears.object") @@ -43,6 +43,10 @@ local action = {} -- @property icon -- @tparam gears.surface|string icon +--- If the action should hide the label and only display the icon. +-- @property icon_only +-- @param[opt=false] boolean + --- The notification. -- @property notification -- @tparam naughty.notification notification @@ -80,7 +84,7 @@ function action:set_position(value) --TODO make sure the position is unique end -for _, prop in ipairs { "name", "icon", "notification" } do +for _, prop in ipairs { "name", "icon", "notification", "icon_only" } do action["get_"..prop] = function(self) return self._private[prop] end @@ -97,6 +101,10 @@ for _, prop in ipairs { "name", "icon", "notification" } do end --- Execute this action. +-- +-- This only emits the `invoked` signal. +-- +-- @method invoke function action:invoke() assert(self._private.notification, "Cannot invoke an action without a notification") diff --git a/lib/naughty/container/background.lua b/lib/naughty/container/background.lua new file mode 100644 index 000000000..5bc97fa06 --- /dev/null +++ b/lib/naughty/container/background.lua @@ -0,0 +1,99 @@ +---------------------------------------------------------------------------- +--- A notification background. +-- +-- This widget holds the boilerplate code associated with the notification +-- background. This includes the color and potentially some other styling +-- elements such as the shape and border. +-- +-- * Honor the `beautiful` notification variables. +-- * React to the `naughty.notification` changes. +-- +--@DOC_wibox_nwidget_background_simple_EXAMPLE@ +-- +-- Note that this widget is based on the `wibox.container.background`. This is +-- an implementation detail and may change in the future without prior notice. +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2019 Emmanuel Lepage Vallee +-- @containermod naughty.widget.background +-- @see wibox.container.background +---------------------------------------------------------------------------- +local wbg = require("wibox.container.background") +local gtable = require("gears.table") +local beautiful = require("beautiful") +local gshape = require("gears.shape") + +local background = {} + +local function update_background(notif, wdg) + local bg = notif.bg or beautiful.notification_bg + local bw = notif.border_width or beautiful.notification_border_width + local bc = notif.border_color or beautiful.notification_border_color + + -- Always fallback to the rectangle to make sure the border works + local shape = notif.shape or + beautiful.notification_shape or gshape.rectangle + + wdg:set_bg(bg) + wdg:set_shape(shape) -- otherwise there's no borders + wdg:set_shape_border_width(bw) + wdg:set_shape_border_color(bc) +end + +--- The attached notification. +-- @property notification +-- @tparam naughty.notification notification + +function background:set_notification(notif) + if self._private.notification == notif then return end + + if self._private.notification then + self._private.notification:disconnect_signal("poperty::bg", + self._private.background_changed_callback) + self._private.notification:disconnect_signal("poperty::border_width", + self._private.background_changed_callback) + self._private.notification:disconnect_signal("poperty::border_color", + self._private.background_changed_callback) + self._private.notification:disconnect_signal("poperty::shape", + self._private.background_changed_callback) + end + + update_background(notif, self) + + self._private.notification = notif + + notif:connect_signal("poperty::bg" , self._private.background_changed_callback) + notif:connect_signal("poperty::border_width", self._private.background_changed_callback) + notif:connect_signal("poperty::border_color", self._private.background_changed_callback) + notif:connect_signal("poperty::shape" , self._private.background_changed_callback) +end + +--- Create a new naughty.container.background. +-- @tparam table args +-- @tparam naughty.notification args.notification The notification. +-- @constructorfct naughty.container.background + +local function new(args) + args = args or {} + + local bg = wbg() + bg:set_border_strategy("inner") + + gtable.crush(bg, background, true) + + function bg._private.background_changed_callback() + update_background(bg._private.notification, bg) + end + + if args.notification then + bg:set_notification(args.notification) + end + + return bg +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(background, {__call = function(_, ...) return new(...) end}) diff --git a/lib/naughty/container/init.lua b/lib/naughty/container/init.lua new file mode 100644 index 000000000..1e3af095e --- /dev/null +++ b/lib/naughty/container/init.lua @@ -0,0 +1,9 @@ +--------------------------------------------------------------------------- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2019 Emmanuel Lepage Vallee +-- @module naughty.container +--------------------------------------------------------------------------- + +return { + background = require( "naughty.container.background" ); +} diff --git a/lib/naughty/core.lua b/lib/naughty/core.lua index 3b93f7745..f57a51d08 100644 --- a/lib/naughty/core.lua +++ b/lib/naughty/core.lua @@ -122,6 +122,10 @@ gtable.crush(naughty, require("naughty.constants")) -- @property active -- @param table +--- True when there is a handler connected to `request::display`. +-- @property has_display_handler +-- @param boolean + local properties = { suspended = false, expiration_paused = false @@ -346,6 +350,10 @@ function naughty.get_active() return naughty._active end +function naughty.get_has_display_handler() + return conns["request::display"] and #conns["request::display"] > 0 or false +end + --- Set new notification timeout. -- -- This function is deprecated, use `notification:reset_timeout(new_timeout)`. @@ -454,7 +462,7 @@ end --- Emitted when a notification has to be displayed. -- --- To add an handler, use: +-- To add a handler, use: -- -- naughty.connect_signal("request::display", function(notification, args) -- -- do something diff --git a/lib/naughty/init.lua b/lib/naughty/init.lua index 2a096df5c..e94f366d3 100644 --- a/lib/naughty/init.lua +++ b/lib/naughty/init.lua @@ -10,7 +10,11 @@ if dbus then end naughty.action = require("naughty.action") +naughty.list = require("naughty.list") naughty.layout = require("naughty.layout") +naughty.widget = require("naughty.widget") +naughty.container = require("naughty.container") +naughty.action = require("naughty.action") naughty.notification = require("naughty.notification") return naughty diff --git a/lib/naughty/layout/box.lua b/lib/naughty/layout/box.lua new file mode 100644 index 000000000..780bd5398 --- /dev/null +++ b/lib/naughty/layout/box.lua @@ -0,0 +1,261 @@ +---------------------------------------------------------------------------- +--- A notification popup widget. +-- +-- By default, the box is composed of many other widgets: +-- +--@DOC_wibox_nwidget_default_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @popupmod naughty.layout.box +---------------------------------------------------------------------------- + +local beautiful = require("beautiful") +local gtable = require("gears.table") +local wibox = require("wibox") +local popup = require("awful.popup") +local awcommon = require("awful.widget.common") +local placement = require("awful.placement") +local abutton = require("awful.button") + +local default_widget = require("naughty.widget._default") + +local box, by_position = {}, {} + +-- Init the weak tables for each positions. It is done ahead of time rather +-- than when notifications are added to simplify the code. +for _, pos in ipairs { "top_left" , "top_middle" , "top_right", + "bottom_left", "bottom_middle", "bottom_right" } do + by_position[pos] = setmetatable({},{__mode = "v"}) +end + +local function get_spacing() + local margin = beautiful.notification_spacing or 2 + return {top = margin, bottom = margin} +end + +-- Leverage `awful.placement` to create the stacks. +local function update_position(position) + local pref = position:match("top_") and "bottom" or "top" + local align = position:match("_(.*)") + :gsub("left", "front"):gsub("right", "back") + + for k, wdg in ipairs(by_position[position]) do + local args = { + geometry = by_position[position][k-1], + preferred_positions = {pref }, + preferred_anchors = {align}, + margins = get_spacing(), + honor_workarea = true, + } + + -- The first entry is aligned to the workarea, then the following to the + -- previous widget. + placement[k==1 and position:gsub("_middle", "") or "next_to"](wdg, args) + + wdg.visible = true + end +end + +local function finish(self) + self.visible = false + assert(by_position[self.position]) + + for k, v in ipairs(by_position[self.position]) do + if v == self then + table.remove(by_position[self.position], k) + break + end + end + + update_position(self.position) +end + +--- The maximum notification width. +-- @beautiful beautiful.notification_max_width +-- @tparam[opt=500] number notification_max_width + +--- The maximum notification position. +-- +-- Valid values are: +-- +-- * top_left +-- * top_middle +-- * top_right +-- * bottom_left +-- * bottom_middle +-- * bottom_right +-- +-- @beautiful beautiful.notification_position +-- @tparam[opt="top_right"] string notification_position + +--- The widget notification object. +-- @property notification +-- @param naughty.notification + +--- The widget template to construct the box content. +-- +--@DOC_wibox_nwidget_default_EXAMPLE@ +-- +-- The default template is (less or more): +-- +-- { +-- { +-- { +-- { +-- { +-- naughty.widget.icon, +-- { +-- naughty.widget.title, +-- naughty.widget.message, +-- spacing = 4, +-- layout = wibox.layout.fixed.vertical, +-- }, +-- fill_space = true, +-- spacing = 4, +-- layout = wibox.layout.fixed.horizontal, +-- }, +-- naughty.list.actions, +-- spacing = 10, +-- layout = wibox.layout.fixed.vertical, +-- }, +-- margins = beautiful.notification_margin, +-- widget = wibox.container.margins, +-- }, +-- id = "background_role", +-- widget = naughty.container.background, +-- }, +-- strategy = "max", +-- width = width(beautiful.notification_max_width +-- or beautiful.xresources.apply_dpi(500) +-- widget = wibox.container.constraint, +-- } +-- +-- @property widget_template +-- @param widget + +local function generate_widget(args, n) + local w = wibox.widget.base.make_widget_from_value( + args.widget_template or default_widget + ) + + -- Call `:set_notification` on all children + awcommon._set_common_property(w, "notification", n or args.notification) + + return w +end + +local function init(self, notification) + local args = self._private.args + + local preset = notification.preset + assert(preset) + + local position = args.position or notification.position or + beautiful.notification_position or preset.position or "top_right" + + if not self.widget then + self.widget = generate_widget(self._private.args, notification) + end + + local bg = self._private.widget:get_children_by_id( "background_role" )[1] + + -- Make sure the border isn't set twice, favor the widget one since it is + -- shared by the notification list and the notification box. + if bg then + if bg.set_notification then + bg:set_notification(notification) + self.border_width = 0 + else + bg:set_bg(notification.bg) + self.border_width = notification.border_width + end + end + + -- Add the notification to the active list + assert(by_position[position]) + + self:_apply_size_now() + + table.insert(by_position[position], self) + + local function update() update_position(position) end + + self:connect_signal("property::geometry", update) + notification:connect_signal("property::margin", update) + notification:connect_signal("destroyed", self._private.destroy_callback) + + update_position(position) + +end + +function box:set_notification(notif) + if self._private.notification == notif then return end + + if self._private.notification then + self._private.notification:disconnect_signal("destroyed", + self._private.destroy_callback) + end + + init(self, notif) + + self._private.notification = notif +end + +function box:get_position() + if self._private.notification then + return self._private.notification:get_position() + end + + return "top_right" +end + +local function new(args) + -- Set the default wibox values + local new_args = { + ontop = true, + visible = false, + bg = args and args.bg or beautiful.notification_bg, + fg = args and args.fg or beautiful.notification_fg, + shape = args and args.shape or beautiful.notification_shape, + border_width = args and args.border_width or beautiful.notification_border_width or 1, + border_color = args and args.border_color or beautiful.notification_border_color, + } + + new_args = args and setmetatable(new_args, {__index = args}) or new_args + + -- Generate the box before the popup is created to avoid the size changing + new_args.widget = generate_widget(new_args) + + local ret = popup(new_args) + ret._private.args = new_args + + gtable.crush(ret, box, true) + + function ret._private.destroy_callback() + finish(ret) + end + + if new_args.notification then + ret:set_notification(new_args.notification) + end + + --TODO remove + local function hide() + if ret._private.notification then + ret._private.notification:destroy() + end + end + + --FIXME there's another pull request for this + ret:buttons(gtable.join( + abutton({ }, 1, hide), + abutton({ }, 3, hide) + )) + + return ret +end + +--@DOC_wibox_COMMON@ + +return setmetatable(box, {__call = function(_, args) return new(args) end}) diff --git a/lib/naughty/layout/init.lua b/lib/naughty/layout/init.lua index c6df968a1..11b187f35 100644 --- a/lib/naughty/layout/init.lua +++ b/lib/naughty/layout/init.lua @@ -5,5 +5,6 @@ --------------------------------------------------------------------------- return { - legacy = require("naughty.layout.legacy") + legacy = require( "naughty.layout.legacy" ); + box = require( "naughty.layout.box" ); } diff --git a/lib/naughty/layout/legacy.lua b/lib/naughty/layout/legacy.lua index c09732906..745ae6de7 100644 --- a/lib/naughty/layout/legacy.lua +++ b/lib/naughty/layout/legacy.lua @@ -13,6 +13,11 @@ -- --@DOC_naughty_actions_EXAMPLE@ -- +-- Use the `naughty.notification.position` property to control where the popup +-- is located. +-- +--@DOC_awful_notification_corner_EXAMPLE@ +-- -- @author koniu <gkusnierz@gmail.com> -- @author Emmanuel Lepage Vallee <elv1313@gmail.com> -- @copyright 2008 koniu @@ -279,23 +284,10 @@ end naughty.connect_signal("destroyed", cleanup) ---- The default notification GUI handler. --- --- To disable this handler, use: --- --- naughty.disconnect_signal( --- "request::display", naughty.default_notification_handler --- ) --- --- It looks like: --- ---@DOC_naughty_actions_EXAMPLE@ --- --- @tparam table notification The `naughty.notification` object. --- @tparam table args Any arguments passed to the `naughty.notify` function, --- including, but not limited to all `naughty.notification` properties. --- @signalhandler naughty.default_notification_handler function naughty.default_notification_handler(notification, args) + -- This is a fallback for users whose config doesn't have the newer + -- `request::display` section. + if naughty.has_display_handler then return end -- If request::display is called more than once, simply make sure the wibox -- is visible. @@ -415,8 +407,14 @@ function naughty.default_notification_handler(notification, args) local action_width = w + 2 * margin actionmarginbox:buttons(gtable.join( - button({ }, 1, function() action:invoke() end), - button({ }, 3, function() action:invoke() end) + button({ }, 1, function() + action:invoke() + notification:destroy() + end), + button({ }, 3, function() + action:invoke() + notification:destroy() + end) )) actionslayout:add(actionmarginbox) @@ -561,4 +559,4 @@ function naughty.default_notification_handler(notification, args) end end -naughty.connect_signal("request::display", naughty.default_notification_handler) +naughty.connect_signal("request::fallback", naughty.default_notification_handler) diff --git a/lib/naughty/list/actions.lua b/lib/naughty/list/actions.lua new file mode 100644 index 000000000..cc37d703c --- /dev/null +++ b/lib/naughty/list/actions.lua @@ -0,0 +1,324 @@ +---------------------------------------------------------------------------- +--- Manage a notification action list. +-- +-- A notification action is a "button" that will trigger an action on the sender +-- process. `notify-send` doesn't support actions, but `libnotify` based +-- applications do. +-- +--@DOC_wibox_nwidget_actionlist_simple_EXAMPLE@ +-- +-- This example has a custom vertical widget template: +-- +--@DOC_wibox_nwidget_actionlist_fancy_EXAMPLE@ +-- +-- This example has a horizontal widget template and icons: +-- +--@DOC_wibox_nwidget_actionlist_fancy_icons_EXAMPLE@ +-- +-- This example uses the theme/style variables instead of the template. This is +-- less flexible, but easier to put in the theme file. Note that each style +-- variable has a `beautiful` equivalent. +-- +--@DOC_wibox_nwidget_actionlist_style_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @widgetmod naughty.list.actions +-- @see awful.widget.common +---------------------------------------------------------------------------- + +local wibox = require("wibox") +local awcommon = require("awful.widget.common") +local abutton = require("awful.button") +local gtable = require("gears.table") +local beautiful= require("beautiful") + +local module = {} + +--- Whether or not to underline the action name. +-- @beautiful beautiful.notification_action_underline_normal +-- @param[opt=true] boolean + +--- Whether or not to underline the selected action name. +-- @beautiful beautiful.notification_action_underline_selected +-- @param[opt=true] boolean + +--- Whether or not the action label should be shown. +-- @beautiful beautiful.notification_action_icon_only +-- @param[opt=false] boolean + +--- Whether or not the action icon should be shown. +-- @beautiful beautiful.notification_action_label_only +-- @param[opt=false] boolean + +--- The shape used for a normal action. +-- @beautiful beautiful.notification_action_shape_normal +-- @tparam[opt=gears.shape.rectangle] gears.shape shape +-- @see gears.shape + +--- The shape used for a selected action. +-- @beautiful beautiful.notification_action_shape_selected +-- @tparam[opt=gears.shape.rectangle] gears.shape shape +-- @see gears.shape + +--- The shape border color for normal actions. +-- @beautiful beautiful.notification_action_shape_border_color_normal +-- @param color +-- @see gears.color + +--- The shape border color for selected actions. +-- @beautiful beautiful.notification_action_shape_border_color_selected +-- @param color +-- @see gears.color + +--- The shape border width for normal actions. +-- @beautiful beautiful.notification_action_shape_border_width_normal +-- @param[opt=0] number + +--- The shape border width for selected actions. +-- @beautiful beautiful.notification_action_shape_border_width_selected +-- @param[opt=0] number + +--- The action icon size. +-- @beautiful beautiful.notification_action_icon_size_normal +-- @param[opt=0] number + +--- The selected action icon size. +-- @beautiful beautiful.notification_action_icon_size_selected +-- @param[opt=0] number + +--- The background color for normal actions. +-- @beautiful beautiful.notification_action_bg_normal +-- @param color +-- @see gears.color + +--- The background color for selected actions. +-- @beautiful beautiful.notification_action_bg_selected +-- @param color +-- @see gears.color + +--- The foreground color for normal actions. +-- @beautiful beautiful.notification_action_fg_normal +-- @param color +-- @see gears.color + +--- The foreground color for selected actions. +-- @beautiful beautiful.notification_action_fg_selected +-- @param color +-- @see gears.color + +--- The background image for normal actions. +-- @beautiful beautiful.notification_action_bgimage_normal +-- @tparam gears.surface|string action_bgimage_normal +-- @see gears.surface + +--- The background image for selected actions. +-- @beautiful beautiful.notification_action_bgimage_selected +-- @tparam gears.surface|string action_bgimage_selected +-- @see gears.surface + +local default_buttons = gtable.join( + abutton({ }, 1, function(a) a:invoke() end) +) + +local props = {"shape_border_color", "bg_image" , "fg", + "shape_border_width", "underline", "bg", + "shape", "icon_size", } + +-- Use a cached loop instead of an large function like the taglist and tasklist +local function update_style(self) + self._private.style_cache = self._private.style_cache or {} + + for _, state in ipairs {"normal", "selected"} do + local s = {} + + for _, prop in ipairs(props) do + if self._private.style[prop.."_"..state] ~= nil then + s[prop] = self._private.style[prop.."_"..state] + else + s[prop] = beautiful["notification_action_"..prop.."_"..state] + end + end + + -- Set a fallback for the icon size to prevent them from being gigantic + s.icon_size = s.icon_size + or beautiful.get_font_height(beautiful.font) * 1.5 + + self._private.style_cache[state] = s + end +end + +local function wb_label(action, self) + -- Get the name + local name = action.name + + local style = self._private.style_cache[action.selected and "selected" or "normal"] + + -- Add the underline + name = style.underline ~= false and + (""..name.."") or name + + local icon = beautiful.notification_action_label_only ~= true and action.icon or nil + + if style.fg then + name = "" .. name .. "" + end + + if action.icon_only or beautiful.notification_action_icon_only then + name = nil + end + + return name, style.bg, style.bg_image, icon, style +end + +local function update(self) + if not self._private.layout or not self._private.notification then return end + + awcommon.list_update( + self._private.layout, + default_buttons, + function(o) return wb_label(o, self) end, + self._private.data, + self._private.notification.actions, + { + widget_template = self._private.widget_template + } + ) +end + +local actionlist = {} + +--- The actionlist parent notification. +-- @property notification +-- @param notification +-- @see naughty.notification + +--- The actionlist layout. +-- If no layout is specified, a `wibox.layout.fixed.horizontal` will be created +-- automatically. +-- @property layout +-- @param widget +-- @see wibox.layout.fixed.horizontal + +--- The actionlist parent notification. +-- @property widget_template +-- @param table + +--- A table with values to override each `beautiful.notification_action` values. +-- @property style +-- @param table + +function actionlist:set_notification(notif) + self._private.notification = notif + + if not self._private.layout then + self._private.layout = wibox.layout.fixed.horizontal() + end + + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function actionlist:set_base_layout(layout) + self._private.layout = layout + + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function actionlist:set_widget_template(widget_template) + self._private.widget_template = widget_template + + -- Remove the existing instances + self._private.data = {} + + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function actionlist:set_style(style) + self._private.style = style or {} + + update_style(self) + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function actionlist:get_notification() + return self._private.notification +end + +function actionlist:layout(_, width, height) + if self._private.layout then + return { wibox.widget.base.place_widget_at(self._private.layout, 0, 0, width, height) } + end +end + +function actionlist:fit(context, width, height) + if not self._private.layout then + return 0, 0 + end + + return wibox.widget.base.fit_widget(self, context, self._private.layout, width, height) +end + +--- Create an action list. +-- +-- @tparam table args +-- @tparam naughty.notification args.notification The notification/ +-- @tparam widget args.base_layout The action layout. +-- @tparam table args.style Override the beautiful values. +-- @tparam boolean args.style.underline_normal +-- @tparam boolean args.style.underline_selected +-- @tparam gears.shape args.style.shape_normal +-- @tparam gears.shape args.style.shape_selected +-- @tparam gears.color|string args.style.shape_border_color_normal +-- @tparam gears.color|string args.style.shape_border_color_selected +-- @tparam number args.style.shape_border_width_normal +-- @tparam number args.style.shape_border_width_selected +-- @tparam number args.style.icon_size +-- @tparam gears.color|string args.style.bg_normal +-- @tparam gears.color|string args.style.bg_selected +-- @tparam gears.color|string args.style.fg_normal +-- @tparam gears.color|string args.style.fg_selected +-- @tparam gears.surface|string args.style.bgimage_normal +-- @tparam gears.surface|string args.style.bgimage_selected +-- @tparam[opt] table widget_template A custom widget to be used for each action. +-- @treturn widget The action widget. +-- @constructorfct naughty.list.actions + +local function new(_, args) + args = args or {} + + local wdg = wibox.widget.base.make_widget(nil, nil, { + enable_properties = true, + }) + + gtable.crush(wdg, actionlist, true) + + wdg._private.data = {} + + gtable.crush(wdg, args) + + wdg._private.style = wdg._private.style or {} + + update_style(wdg) + + return wdg +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(module, {__call = new}) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/naughty/list/init.lua b/lib/naughty/list/init.lua new file mode 100644 index 000000000..46470f127 --- /dev/null +++ b/lib/naughty/list/init.lua @@ -0,0 +1,10 @@ +--------------------------------------------------------------------------- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017-2019 Emmanuel Lepage Vallee +-- @module naughty.list +--------------------------------------------------------------------------- + +return { + actions = require( "naughty.list.actions" ); + notifications = require( "naughty.list.notifications" ); +} diff --git a/lib/naughty/list/notifications.lua b/lib/naughty/list/notifications.lua new file mode 100644 index 000000000..3e388ccc4 --- /dev/null +++ b/lib/naughty/list/notifications.lua @@ -0,0 +1,356 @@ +---------------------------------------------------------------------------- +--- Get a list of all currently active notifications. +-- +-- @DOC_awful_notification_notificationlist_bottombar_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @widgetmod naughty.list.notifications +-- @see awful.widget.common +---------------------------------------------------------------------------- + +local wibox = require("wibox") +local awcommon = require("awful.widget.common") +local abutton = require("awful.button") +local gtable = require("gears.table") +local gtimer = require("gears.timer") +local beautiful= require("beautiful") +local naughty = require("naughty.core") + +local default_widget = require("naughty.widget._default") + +local module = {} + +--- The shape used for a normal notification. +-- @beautiful beautiful.notification_shape_normal +-- @tparam[opt=gears.shape.rectangle] gears.shape shape +-- @see gears.shape + +--- The shape used for a selected notification. +-- @beautiful beautiful.notification_shape_selected +-- @tparam[opt=gears.shape.rectangle] gears.shape shape +-- @see gears.shape + +--- The shape border color for normal notifications. +-- @beautiful beautiful.notification_shape_border_color_normal +-- @param color +-- @see gears.color + +--- The shape border color for selected notifications. +-- @beautiful beautiful.notification_shape_border_color_selected +-- @param color +-- @see gears.color + +--- The shape border width for normal notifications. +-- @beautiful beautiful.notification_shape_border_width_normal +-- @param[opt=0] number + +--- The shape border width for selected notifications. +-- @beautiful beautiful.notification_shape_border_width_selected +-- @param[opt=0] number + +--- The notification icon size. +-- @beautiful beautiful.notification_icon_size_normal +-- @param[opt=0] number + +--- The selected notification icon size. +-- @beautiful beautiful.notification_icon_size_selected +-- @param[opt=0] number + +--- The background color for normal notifications. +-- @beautiful beautiful.notification_bg_normal +-- @param color +-- @see gears.color + +--- The background color for selected notifications. +-- @beautiful beautiful.notification_bg_selected +-- @param color +-- @see gears.color + +--- The foreground color for normal notifications. +-- @beautiful beautiful.notification_fg_normal +-- @param color +-- @see gears.color + +--- The foreground color for selected notifications. +-- @beautiful beautiful.notification_fg_selected +-- @param color +-- @see gears.color + +--- The background image for normal notifications. +-- @beautiful beautiful.notification_bgimage_normal +-- @tparam string|gears.surface bgimage_normal +-- @see gears.surface + +--- The background image for selected notifications. +-- @beautiful beautiful.notification_bgimage_selected +-- @tparam string|gears.surface bgimage_selected +-- @see gears.surface + +local default_buttons = gtable.join( + abutton({ }, 1, function(n) n:destroy() end), + abutton({ }, 3, function(n) n:destroy() end) +) + +local props = {"shape_border_color", "bg_image" , "fg", + "shape_border_width", "shape" , "bg", + "icon_size"} + +-- Use a cached loop instead of an large function like the taglist and tasklist +local function update_style(self) + self._private.style_cache = self._private.style_cache or {} + + for _, state in ipairs {"normal", "selected"} do + local s = {} + + for _, prop in ipairs(props) do + if self._private.style[prop.."_"..state] ~= nil then + s[prop] = self._private.style[prop.."_"..state] + else + s[prop] = beautiful["notification_"..prop.."_"..state] + end + end + + self._private.style_cache[state] = s + end +end + +local function wb_label(notification, self) + -- Get the title + local title = notification.title + + local style = self._private.style_cache[notification.selected and "selected" or "normal"] + + if notification.fg or style.fg then + title = "" .. title .. "" + end + + return title, notification.bg or style.bg, style.bg_image, notification.icon, { + shape = notification.shape or style.shape, + shape_border_width = notification.border_width or style.shape_border_width, + shape_border_color = notification.border_color or style.shape_border_color, + icon_size = style.icon_size, + } +end + +-- Remove some callback boilerplate from the user provided templates. +local function create_callback(w, n) + awcommon._set_common_property(w, "notification", n) +end + +local function update(self) + -- Checking style_cache helps to avoid useless redraw during initialization + if not self._private.base_layout or not self._private.style_cache then return end + + awcommon.list_update( + self._private.base_layout, + default_buttons, + function(o) return wb_label(o, self) end, + self._private.data, + naughty.active, + { + create_callback = create_callback, + widget_template = self._private.widget_template or default_widget + } + ) +end + +local notificationlist = {} + +--- The notificationlist parent notification. +-- @property notification +-- @param notification +-- @see naughty.notification + +--- The notificationlist layout. +-- If no layout is specified, a `wibox.layout.fixed.vertical` will be created +-- automatically. +-- @property layout +-- @param widget +-- @see wibox.layout.fixed.vertical + +--- The notificationlist parent notification. +-- @property widget_template +-- @param table + +--- A table with values to override each `beautiful.notification_action` values. +-- @property style +-- @param table + +function notificationlist:set_widget_template(widget_template) + self._private.widget_template = widget_template + + -- Remove the existing instances + self._private.data = {} + + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function notificationlist:set_style(style) + self._private.style = style or {} + + update_style(self) + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") +end + +function notificationlist:layout(_, width, height) + if self._private.base_layout then + return { wibox.widget.base.place_widget_at(self._private.base_layout, 0, 0, width, height) } + end +end + +function notificationlist:fit(context, width, height) + if not self._private.base_layout then + return 0, 0 + end + + return wibox.widget.base.fit_widget(self, context, self._private.base_layout, width, height) +end + +--- A `wibox.layout` to be used to place the entries. +-- @property base_layout +-- @param widget +-- @see wibox.layout.fixed.horizontal +-- @see wibox.layout.fixed.vertical +-- @see wibox.layout.flex.horizontal +-- @see wibox.layout.flex.vertical +-- @see wibox.layout.grid + +--- A function to prevent some notifications from being added to the list. +-- @property filter +-- @param function + +for _, prop in ipairs { "filter", "client", "clients", "tag", "tags", "screen", "base_layout" } do + notificationlist["set_"..prop] = function(self, value) + self._private[prop] = value + + update(self) + + self:emit_signal("widget::layout_changed") + self:emit_signal("widget::redraw_needed") + end + + notificationlist["get_"..prop] = function(self) + return self._private[prop] + end +end + +--- Create an notification list. +-- +-- @tparam table args +-- @tparam widget args.base_layout The notification list base_layout. +-- @tparam widget args.filter The list filter. +-- @tparam table args.style Override the beautiful values. +-- @tparam gears.shape args.style.shape_normal +-- @tparam gears.shape args.style.shape_selected +-- @tparam gears.color|string args.style.shape_border_color_normal +-- @tparam gears.color|string args.style.shape_border_color_selected +-- @tparam number args.style.shape_border_width_normal +-- @tparam number args.style.shape_border_width_selected +-- @tparam number args.style.icon_size +-- @tparam gears.color|string args.style.bg_normal +-- @tparam gears.color|string args.style.bg_selected +-- @tparam gears.color|string args.style.fg_normal +-- @tparam gears.color|string args.style.fg_selected +-- @tparam gears.surface|string args.style.bgimage_normal +-- @tparam gears.surface|string args.style.bgimage_selected +-- @tparam[opt] table widget_template A custom widget to be used for each +-- notifications. +-- @treturn widget The notification list widget. +-- @constructorfct naughty.list.notifications + +local function new(_, args) + args = args or {} + + local wdg = wibox.widget.base.make_widget(nil, nil, { + enable_properties = true, + }) + + gtable.crush(wdg, notificationlist, true) + + wdg._private.data = {} + + gtable.crush(wdg, args) + + wdg._private.style = wdg._private.style or {} + + -- Don't do this right away since the base_layout may not have been set yet. + -- This also avoids `update()` being executed during initialization and + -- causing an output that isn't reproducible. + gtimer.delayed_call(function() + update_style(wdg) + + if not wdg._private.base_layout then + wdg._private.base_layout = wibox.layout.flex.horizontal() + wdg._private.base_layout:set_spacing(beautiful.notification_spacing or 0) + wdg:emit_signal("widget::layout_changed") + wdg:emit_signal("widget::redraw_needed") + end + + update(wdg) + + local is_scheduled = false + + -- Prevent multiple updates due to the many signals. + local function f() + if is_scheduled then return end + + is_scheduled = true + + gtimer.delayed_call(function() update(wdg); is_scheduled = false end) + end + + -- Yes, this will cause 2 updates when a new notification arrives, but + -- on the other hand, request::display is required to auto-disable the + -- fallback legacy mode and property::active is needed to remove the + -- destroyed notifications. + naughty.connect_signal("property::active", f) + naughty.connect_signal("request::display", f) + end) + + return wdg +end + +module.filter = {} + +--- +-- @param n The notification. +-- @return Always returns true because it doesn't filter anything at all. +-- @filterfunction naughty.list.notifications.filter.all +function module.filter.all(n) -- luacheck: no unused args + return true +end + +--- Only get the most recent notification(s). +-- +-- To set the count, the function needs to be wrapped: +-- +-- filter = function(n) return naughty.list.notifications.filter.most_recent(n, 3) end +-- +-- @param n The notification. +-- @tparam[opt=1] number count The number of recent notifications to allow. +-- @return Always returns true because it doesn't filter anything at all. +-- @filterfunction naughty.list.notifications.filter.most_recent +function module.filter.most_recent(n, count) + for i=1, count or 1 do + if n == naughty.active[i] then + return true + end + end + + return false +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(module, {__call = new}) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/lib/naughty/notification.lua b/lib/naughty/notification.lua index d7f2a77cc..5004cfc71 100644 --- a/lib/naughty/notification.lua +++ b/lib/naughty/notification.lua @@ -53,7 +53,7 @@ local notification = {} -- @beautiful beautiful.notification_opacity -- @tparam[opt] int notification_opacity ---- Notifications margin. +--- The margins inside of the notification widget (or popup). -- @beautiful beautiful.notification_margin -- @tparam int notification_margin @@ -65,6 +65,11 @@ local notification = {} -- @beautiful beautiful.notification_height -- @tparam int notification_height +--- The spacing between the notifications. +-- @beautiful beautiful.notification_spacing +-- @param[opt=2] number +-- @see gears.surface + -- Unique identifier of the notification. -- This is the equivalent to a PID as allows external applications to select -- notifications. @@ -72,8 +77,12 @@ local notification = {} -- @param string -- @see title --- Text of the notification [[deprecated]] --- @property text +--- Text of the notification. +-- +-- This exists only for the pre-AwesomeWM v4.4 new notification implementation. +-- Please always use `title`. +-- +-- @deprecatedproperty text -- @param string -- @see title @@ -107,22 +116,28 @@ local notification = {} -- * *bottom_middle* -- * *middle* -- ---@DOC_awful_notification_corner_EXAMPLE@ +--@DOC_awful_notification_box_corner_EXAMPLE@ -- -- @property position -- @param string +-- @see awful.placement.next_to --- Boolean forcing popups to display on top. -- @property ontop -- @param boolean --- Popup height. +-- +--@DOC_awful_notification_geometry_EXAMPLE@ +-- -- @property height -- @param number +-- @see width --- Popup width. -- @property width -- @param number +-- @see height --- Notification font. --@DOC_naughty_colors_EXAMPLE@ @@ -138,12 +153,18 @@ local notification = {} -- @param number --- Foreground color. +-- +--@DOC_awful_notification_fg_EXAMPLE@ +-- -- @property fg -- @tparam string|color|pattern fg -- @see title -- @see gears.color --- Background color. +-- +--@DOC_awful_notification_bg_EXAMPLE@ +-- -- @property bg -- @tparam string|color|pattern bg -- @see title @@ -155,32 +176,58 @@ local notification = {} -- @see title --- Border color. +-- +--@DOC_awful_notification_border_color_EXAMPLE@ +-- -- @property border_color -- @param string -- @see title -- @see gears.color --- Widget shape. +-- +-- Note that when using a custom `request::display` handler or `naughty.rules`, +-- choosing between multiple shapes depending on the content can be done using +-- expressions like: +-- +-- -- The notification object is called `n` +-- shape = #n.actions > 0 and +-- gears.shape.rounded_rect or gears.shape.rounded_bar, +-- +--@DOC_awful_notification_shape_EXAMPLE@ +-- --@DOC_naughty_shape_EXAMPLE@ +-- -- @property shape --- @param gears.shape +-- @tparam gears.shape shape --- Widget opacity. -- @property opacity --- @param number From 0 to 1 +-- @tparam number opacity Between 0 to 1. --- Widget margin. +-- +--@DOC_awful_notification_margin_EXAMPLE@ +-- -- @property margin -- @tparam number|table margin -- @see shape --- Function to run on left click. --- @property run +-- +-- Use the signals rather than this. +-- +-- @deprecatedproperty run -- @param function +-- @see destroyed --- Function to run when notification is destroyed. --- @property destroy +-- +-- Use the signals rather than this. +-- +-- @deprecatedproperty destroy -- @param function +-- @see destroyed --- Table with any of the above parameters. -- args will override ones defined @@ -380,16 +427,18 @@ local function convert_actions(actions) local naction = require("naughty.action") + local new_actions = {} + -- Does not attempt to handle when there is a mix of strings and objects for idx, name in pairs(actions) do - local cb = nil + local cb, old_idx = nil, idx if type(name) == "function" then cb = name end if type(idx) == "string" then - name, idx = idx, nil + name, idx = idx, #actions+1 end local a = naction { @@ -401,9 +450,14 @@ local function convert_actions(actions) a:connect_signal("invoked", cb) end - -- Yes, it modifies `args`, this is legacy code, cloning the args - -- just for this isn't worth it. - actions[idx] = a + new_actions[old_idx] = a + end + + -- Yes, it modifies `args`, this is legacy code, cloning the args + -- just for this isn't worth it. + for old_idx, a in pairs(new_actions) do + actions[a.position] = a + actions[ old_idx ] = nil end end @@ -497,7 +551,7 @@ local function create(args) rawget(n, "preset") or {} )) - if is_old_action then + if is_old_action then convert_actions(args.actions) end @@ -509,6 +563,16 @@ local function create(args) private[k] = v end + -- notif.actions should not be nil to allow cheching if there is actions + -- using the shorthand `if #notif.actions > 0 then` + private.actions = private.actions or {} + + -- Make sure the action are for this notification. Sharing actions with + -- multiple notification is not supported. + for _, a in ipairs(private.actions) do + a.notification = n + end + -- It's an automatic property n.is_expired = false @@ -524,7 +588,8 @@ local function create(args) -- Let all listeners handle the actual visual aspects if (not n.ignore) and (not n.preset.ignore) then - naughty.emit_signal("request::display", n, args) + naughty.emit_signal("request::display" , n, args) + naughty.emit_signal("request::fallback", n, args) end -- Because otherwise the setter logic would not be executed diff --git a/lib/naughty/widget/_default.lua b/lib/naughty/widget/_default.lua new file mode 100644 index 000000000..c964d9da6 --- /dev/null +++ b/lib/naughty/widget/_default.lua @@ -0,0 +1,85 @@ +---------------------------------------------------------------------------- +--- The default widget template for the notifications. +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2019 Emmanuel Lepage Vallee +-- @classmod naughty.widget._default +---------------------------------------------------------------------------- + +local wibox = require("wibox") +local actionlist = require("naughty.list.actions") +local wtitle = require("naughty.widget.title") +local wmessage = require("naughty.widget.message") +local wicon = require("naughty.widget.icon") +local wbg = require("naughty.container.background") +local beautiful = require("beautiful") +local dpi = require("beautiful").xresources.apply_dpi + +-- It is not worth doing a special widget for this. +local function notif_size() + local constraint = wibox.container.constraint() + constraint:set_strategy("max") + constraint:set_width(beautiful.notification_max_width or dpi(500)) + + rawset(constraint, "set_notification", function(_, notif) + constraint._private.notification = notif + local s = false + + if notif.width and notif.width ~= beautiful.notification_max_width then + constraint.width = notif.width + s = true + end + if notif.height then + constraint.height = notif.height + s = true + end + + constraint.strategy = s and "exact" or "max" + end) + + return constraint +end + +-- It is not worth doing a special widget for this either. +local function notif_margins() + local margins = wibox.container.margin() + margins:set_margins(beautiful.notification_margin or 4) + + rawset(margins, "set_notification", function(_, notif) + if notif.margin then + margins:set_margins(notif.margin) + end + end) + + return margins +end + +-- Used as a fallback when no widget_template is provided, emulate the legacy +-- widget. +return { + { + { + { + { + wicon, + { + wtitle, + wmessage, + spacing = 4, + layout = wibox.layout.fixed.vertical, + }, + fill_space = true, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + }, + actionlist, + spacing = 10, + layout = wibox.layout.fixed.vertical, + }, + widget = notif_margins, + }, + id = "background_role", + widget = wbg, + }, + widget = notif_size, +} diff --git a/lib/naughty/widget/icon.lua b/lib/naughty/widget/icon.lua new file mode 100644 index 000000000..cac135645 --- /dev/null +++ b/lib/naughty/widget/icon.lua @@ -0,0 +1,158 @@ +---------------------------------------------------------------------------- +--- A notification square icon. +-- +-- This widget is a specialized `wibox.widget.imagebox` with the following extra +-- features: +-- +-- * Honor the `beautiful` notification variables. +-- * Restrict the size avoid huge notifications +-- * Provides some strategies to handle small icons +-- * React to the `naughty.notification` object icon changes. +-- +--@DOC_wibox_nwidget_icon_simple_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @widgetmod naughty.widget.icon +-- @see wibox.widget.imagebox +---------------------------------------------------------------------------- +local imagebox = require("wibox.widget.imagebox") +local gtable = require("gears.table") +local beautiful = require("beautiful") +local dpi = require("beautiful.xresources").apply_dpi + +local icon = {} + +-- The default way to resize the icon. +-- @beautiful beautiful.notification_icon_resize_strategy +-- @param number + +function icon:fit(_, width, height) + -- Until someone complains, adding a "leave blank space" isn't supported + if not self._private.image then return 0, 0 end + + local maximum = math.min(width, height) + local strategy = self._private.resize_strategy or "resize" + local optimal = math.min(beautiful.notification_icon_size or dpi(48), maximum) + + local w = self._private.image:get_width() + local h = self._private.image:get_height() + + if strategy == "resize" then + return math.min(w, optimal, maximum), math.min(h, optimal, maximum) + else + return optimal, optimal + end +end + +function icon:draw(_, cr, width, height) + if not self._private.image then return end + if width == 0 or height == 0 then return end + + -- Let's scale the image so that it fits into (width, height) + local strategy = self._private.resize_strategy or "resize" + local w = self._private.image:get_width() + local h = self._private.image:get_height() + local aspect = width / w + local aspect_h = height / h + + if aspect > aspect_h then aspect = aspect_h end + + if aspect < 1 or (strategy == "scale" and (w < width or h < height)) then + cr:scale(aspect, aspect) + end + + local x, y = 0, 0 + + if (strategy == "center" and aspect > 1) or strategy == "resize" then + x = math.floor((width - w) / 2) + y = math.floor((height - h) / 2) + end + + cr:set_source_surface(self._private.image, x, y) + cr:paint() +end + +--- The attached notification. +-- @property notification +-- @tparam naughty.notification notification + +function icon:set_notification(notif) + if self._private.notification == notif then return end + + if self._private.notification then + self._private.notification:disconnect_signal("destroyed", + self._private.icon_changed_callback) + end + + self:set_image(notif.icon) + + self._private.notification = notif + + notif:connect_signal("poperty::icon", self._private.icon_changed_callback) +end + +local valid_strategies = { + scale = true, + center = true, + resize = true, +} + +--- How small icons are handled. +-- +-- Valid values are: +-- +-- * **scale**: Scale the icon up to the optimal size. +-- * **center**: Keep the icon size and draw it in the center +-- * **resize**: Change the size of the widget itself (*default*). +-- +-- Note that the size upper bound is defined by +-- `beautiful.notification_icon_size`. +-- +--@DOC_wibox_nwidget_icon_strategy_EXAMPLE@ +-- +-- @property resize_strategy +-- @param string + +function icon:set_resize_strategy(strategy) + assert(valid_strategies[strategy], "Invalid strategy") + + self._private.resize_strategy = strategy + + self:emit_signal("widget::redraw_needed") +end + + +function icon:get_resize_strategy() + return self._private.resize_strategy + or beautiful.notification_icon_resize_strategy + or "resize" +end + +--- Create a new naughty.widget.icon. +-- @tparam table args +-- @tparam naughty.notification args.notification The notification. +-- @constructorfct naughty.widget.icon + +local function new(args) + args = args or {} + local tb = imagebox() + + gtable.crush(tb, icon, true) + + function tb._private.icon_changed_callback() + tb:set_image(tb._private.notification.icon) + end + + if args.notification then + tb:set_notification(args.notification) + end + + return tb +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(icon, {__call = function(_, ...) return new(...) end}) diff --git a/lib/naughty/widget/init.lua b/lib/naughty/widget/init.lua new file mode 100644 index 000000000..85858f9ab --- /dev/null +++ b/lib/naughty/widget/init.lua @@ -0,0 +1,11 @@ +--------------------------------------------------------------------------- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @module naughty.widget +--------------------------------------------------------------------------- + +return { + title = require( "naughty.widget.title" ); + icon = require( "naughty.widget.icon" ); + message = require( "naughty.widget.message" ); +} diff --git a/lib/naughty/widget/message.lua b/lib/naughty/widget/message.lua new file mode 100644 index 000000000..8a151b87c --- /dev/null +++ b/lib/naughty/widget/message.lua @@ -0,0 +1,86 @@ +---------------------------------------------------------------------------- +--- A notification content message. +-- +-- This widget is a specialized `wibox.widget.textbox` with the following extra +-- features: +-- +-- * Honor the `beautiful` notification variables. +-- * React to the `naughty.notification` object message changes. +-- +--@DOC_wibox_nwidget_message_simple_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @widgetmod naughty.widget.message +-- @see wibox.widget.textbox +---------------------------------------------------------------------------- +local textbox = require("wibox.widget.textbox") +local gtable = require("gears.table") +local beautiful = require("beautiful") + +local message = {} + +local function markup(notif, wdg) + local ret = notif.message or "" + local fg = notif.fg or beautiful.notification_fg + + wdg:set_font(notif.font or beautiful.notification_font) + + if fg then + ret = "" .. ret .. "" + end + + return ret +end + +--- The attached notification. +-- @property notification +-- @tparam naughty.notification notification + +function message:set_notification(notif) + if self._private.notification == notif then return end + + if self._private.notification then + self._private.notification:disconnect_signal("poperty::message", + self._private.message_changed_callback) + self._private.notification:disconnect_signal("poperty::fg", + self._private.message_changed_callback) + end + + self:set_markup(markup(notif, self)) + + self._private.notification = notif + + notif:connect_signal("poperty::message", self._private.message_changed_callback) + notif:connect_signal("poperty::fg" , self._private.message_changed_callback) +end + +--- Create a new naughty.widget.message. +-- @tparam table args +-- @tparam naughty.notification args.notification The notification. +-- @constructorfct naughty.widget.message + +local function new(args) + args = args or {} + local tb = textbox() + tb:set_wrap("word") + tb:set_font(beautiful.notification_font) + + gtable.crush(tb, message, true) + + function tb._private.message_changed_callback() + tb:set_markup(markup(tb._private.notification, tb)) + end + + if args.notification then + tb:set_notification(args.notification) + end + + return tb +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(message, {__call = function(_, ...) return new(...) end}) diff --git a/lib/naughty/widget/title.lua b/lib/naughty/widget/title.lua new file mode 100644 index 000000000..92bf4763f --- /dev/null +++ b/lib/naughty/widget/title.lua @@ -0,0 +1,87 @@ +---------------------------------------------------------------------------- +--- A notification title. +-- +-- This widget is a specialized `wibox.widget.textbox` with the following extra +-- features: +-- +-- * Honor the `beautiful` notification variables. +-- * React to the `naughty.notification` object title changes. +-- +--@DOC_wibox_nwidget_title_simple_EXAMPLE@ +-- +-- @author Emmanuel Lepage Vallee <elv1313@gmail.com> +-- @copyright 2017 Emmanuel Lepage Vallee +-- @widgetmod naughty.widget.title +-- @see wibox.widget.textbox +---------------------------------------------------------------------------- +local textbox = require("wibox.widget.textbox") +local gtable = require("gears.table") +local beautiful = require("beautiful") + +local title = {} + +local function markup(notif, wdg) + local ret = ""..(notif.title or "").."" + local fg = notif.fg or beautiful.notification_fg + + wdg:set_font(notif.font or beautiful.notification_font) + + if fg then + ret = "" .. ret .. "" + end + + return ret +end + +--- The attached notification. +-- @property notification +-- @tparam naughty.notification notification + +function title:set_notification(notif) + if self._private.notification == notif then return end + + if self._private.notification then + self._private.notification:disconnect_signal("poperty::message", + self._private.title_changed_callback) + self._private.notification:disconnect_signal("poperty::fg", + self._private.title_changed_callback) + end + + self:set_markup(markup(notif, self)) + + self._private.notification = notif + self._private.title_changed_callback() + + notif:connect_signal("poperty::title", self._private.title_changed_callback) + notif:connect_signal("poperty::fg" , self._private.title_changed_callback) +end + +--- Create a new naughty.widget.title. +-- @tparam table args +-- @tparam naughty.notification args.notification The notification. +-- @constructorfct naughty.widget.title + +local function new(args) + args = args or {} + local tb = textbox() + tb:set_wrap("word") + tb:set_font(beautiful.notification_font) + + gtable.crush(tb, title, true) + + function tb._private.title_changed_callback() + tb:set_markup(markup(tb._private.notification, tb)) + end + + if args.notification then + tb:set_notification(args.notification) + end + + return tb +end + +--@DOC_widget_COMMON@ + +--@DOC_object_COMMON@ + +return setmetatable(title, {__call = function(_, ...) return new(...) end}) diff --git a/tests/examples/awful/notification/bg.lua b/tests/examples/awful/notification/bg.lua new file mode 100644 index 000000000..8762be5a7 --- /dev/null +++ b/tests/examples/awful/notification/bg.lua @@ -0,0 +1,31 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE +require("_default_look") --DOC_HIDE +local awful = {wibar = require("awful.wibar")} --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +local some_wibar = awful.wibar {position = "bottom", height = 48, visible = true} --DOC_HIDE + +--DOC_NEWLINE + + -- A notification popup using the default widget_template. + naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} + end) + +--DOC_NEWLINE + + -- Notifications as widgets for any `wibox`/`awful.wibar`/`awful.popup` + some_wibar.widget = naughty.list.notifications {} + +--DOC_NEWLINE + + for _, color in ipairs {"#ff0000", "#00ff00", "#0000ff"} do + naughty.notification { + title = "A ".. color .." notification", + bg = color, + } + end + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/notification/border_color.lua b/tests/examples/awful/notification/border_color.lua new file mode 100644 index 000000000..d89affe11 --- /dev/null +++ b/tests/examples/awful/notification/border_color.lua @@ -0,0 +1,32 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE +require("_default_look") --DOC_HIDE +local awful = {wibar = require("awful.wibar")} --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +local some_wibar = awful.wibar {position = "bottom", height = 48, visible = true} --DOC_HIDE + +--DOC_NEWLINE + + -- A notification popup using the default widget_template. + naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} + end) + +--DOC_NEWLINE + + -- Notifications as widgets for any `wibox`/`awful.wibar`/`awful.popup` + some_wibar.widget = naughty.list.notifications {} + +--DOC_NEWLINE + + for bw, color in ipairs {"#ff0000", "#00ff00", "#0000ff"} do + naughty.notification { + title = "A ".. color .." notification", + border_color = color, + border_width = bw*2, + } + end + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/notification/box_corner.lua b/tests/examples/awful/notification/box_corner.lua new file mode 100644 index 000000000..d1f316c61 --- /dev/null +++ b/tests/examples/awful/notification/box_corner.lua @@ -0,0 +1,37 @@ +--DOC_HIDE --DOC_GEN_IMAGE --DOC_NO_USAGE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 480} --DOC_HIDE +require("_date") --DOC_HIDE +require("_default_look") --DOC_HIDE + +local function ever_longer_messages(iter) --DOC_HIDE + local ret = "content! " --DOC_HIDE + for _=1, iter do --DOC_HIDE + ret = ret.."more! " --DOC_HIDE + end --DOC_HIDE + return ret --DOC_HIDE +end --DOC_HIDE + +--DOC_NEWLINE + +naughty.connect_signal("request::display", function(n) --DOC_HIDE + naughty.layout.box {notification = n} --DOC_HIDE +end) --DOC_HIDE + +--DOC_NEWLINE + + for _, pos in ipairs { + "top_left" , "top_middle" , "top_right", + "bottom_left", "bottom_middle", "bottom_right", + } do + for i=1, 3 do + naughty.notification { + position = pos, + title = pos .. " " .. i, + message = ever_longer_messages(i) + } + end + end + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/notification/box_corner.output.txt b/tests/examples/awful/notification/box_corner.output.txt new file mode 100644 index 000000000..0e74ebd85 --- /dev/null +++ b/tests/examples/awful/notification/box_corner.output.txt @@ -0,0 +1,4 @@ + + + +====================VVVVVV table: 0x5602abbcedd0 diff --git a/tests/examples/awful/notification/corner.lua b/tests/examples/awful/notification/corner.lua index 539d0b773..b0722be1a 100644 --- a/tests/examples/awful/notification/corner.lua +++ b/tests/examples/awful/notification/corner.lua @@ -1,4 +1,5 @@ --DOC_HIDE_ALL +--DOC_GEN_IMAGE local naughty = require("naughty") --DOC_HIDE for _, pos in ipairs { @@ -13,7 +14,7 @@ for _, pos in ipairs { naughty.notify { title = pos, position = pos, - text = "", + message = "", } end diff --git a/tests/examples/awful/notification/fg.lua b/tests/examples/awful/notification/fg.lua new file mode 100644 index 000000000..b3f461e85 --- /dev/null +++ b/tests/examples/awful/notification/fg.lua @@ -0,0 +1,32 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE +require("_default_look") --DOC_HIDE +local awful = {wibar = require("awful.wibar")} --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +local some_wibar = awful.wibar {position = "bottom", height = 48, visible = true} --DOC_HIDE + +--DOC_NEWLINE + + -- A notification popup using the default widget_template. + naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} + end) + +--DOC_NEWLINE + + -- Notifications as widgets for any `wibox`/`awful.wibar`/`awful.popup` + some_wibar.widget = naughty.list.notifications {} + +--DOC_NEWLINE + + for _, color in ipairs {"#ff0000", "#00ff00", "#0000ff"} do + naughty.notification { + title = "A ".. color .." notification", + message = "Message", + fg = color, + } + end + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/notification/geometry.lua b/tests/examples/awful/notification/geometry.lua new file mode 100644 index 000000000..a6a3aab2e --- /dev/null +++ b/tests/examples/awful/notification/geometry.lua @@ -0,0 +1,30 @@ +--DOC_HIDE --DOC_GEN_IMAGE --DOC_NO_USAGE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 480} --DOC_HIDE +require("_date") --DOC_HIDE +require("_default_look") --DOC_HIDE + +naughty.connect_signal("request::display", function(n) --DOC_HIDE + naughty.layout.box {notification = n} --DOC_HIDE +end) --DOC_HIDE + +--DOC_NEWLINE + + for _, pos in ipairs { + "top_left" , "top_middle" , "top_right", + "bottom_left", "bottom_middle", "bottom_right", + } do + for i=1, 2 do + naughty.notification { + position = pos, + title = pos .. " " .. i, + width = 50*i, + height = 50*i, + message = "This is a very, very, very, ".. + "very, very very long message", + } + end + end + +--DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/awful/notification/margin.lua b/tests/examples/awful/notification/margin.lua new file mode 100644 index 000000000..ac0da4eac --- /dev/null +++ b/tests/examples/awful/notification/margin.lua @@ -0,0 +1,40 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE +require("_default_look") --DOC_HIDE +local awful = {wibar = require("awful.wibar")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +local some_wibar = awful.wibar {position = "bottom", height = 48, visible = true} --DOC_HIDE + +--DOC_NEWLINE + + -- A notification popup using the default widget_template. + naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} + end) + +--DOC_NEWLINE + + -- Notifications as widgets for any `wibox`/`awful.wibar`/`awful.popup` + some_wibar.widget = naughty.list.notifications { + base_layout = wibox.widget { + spacing = beautiful.notification_spacing, + layout = wibox.layout.fixed.horizontal + }, + } + +--DOC_NEWLINE + + for margin = 10, 20, 5 do + naughty.notification { + title = "A notification", + margin = margin, + border_width = 1, + border_color = "#ff0000", + } + end + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/notification/notificationlist/bottombar.lua b/tests/examples/awful/notification/notificationlist/bottombar.lua new file mode 100644 index 000000000..7e16f78c0 --- /dev/null +++ b/tests/examples/awful/notification/notificationlist/bottombar.lua @@ -0,0 +1,129 @@ +--DOC_GEN_IMAGE +--DOC_NO_USAGE +require("_date") --DOC_HIDE +require("_default_look") --DOC_HIDE +local awful = require("awful") --DOC_HIDE +local gears = require("gears") --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +--DOC_HIDE Give some context, otherwise it doesn't look like a screen +local c = client.gen_fake {hide_first=true} --DOC_HIDE +c:geometry { x = 50, y = 45, height = 100, width = 250} --DOC_HIDE +c._old_geo = {c:geometry()} --DOC_HIDE +c:set_label("A client") --DOC_HIDE +c:emit_signal("request::titlebars", "rules", {})--DOC_HIDE + +beautiful.notification_icon_size = 48 --DOC_HIDE +beautiful.notification_action_label_only = true --DOC_HIDE + +--DOC_NEWLINE + + -- This awful.wibar will be placed at the bottom and contain the notifications. + local notif_wb = awful.wibar { + position = "bottom", + height = 48, + visible = #naughty.active > 0, + } + +--DOC_NEWLINE + + notif_wb:setup { + nil, + { + base_layout = wibox.widget { + spacing_widget = wibox.widget { + orientation = "vertical", + span_ratio = 0.5, + widget = wibox.widget.separator, + }, + forced_height = 30, + spacing = 3, + layout = wibox.layout.flex.horizontal + }, + widget_template = { + { + naughty.widget.icon, + { + naughty.widget.title, + naughty.widget.message, + { + layout = wibox.widget { + -- Adding the `wibox.widget` allows to share a + -- single instance for all spacers. + spacing_widget = wibox.widget { + orientation = "vertical", + span_ratio = 0.9, + widget = wibox.widget.separator, + }, + spacing = 3, + layout = wibox.layout.flex.horizontal + }, + widget = naughty.list.widgets, + }, + layout = wibox.layout.align.vertical + }, + spacing = 10, + fill_space = true, + layout = wibox.layout.fixed.horizontal + }, + margins = 5, + widget = wibox.container.margin + }, + widget = naughty.list.notifications, + }, + -- Add a button to dismiss all notifications, because why not. + { + { + text = "Dismiss all", + align = "center", + valign = "center", + widget = wibox.widget.textbox + }, + buttons = gears.table.join( + awful.button({ }, 1, function() naughty.destroy_all_notifications() end) + ), + forced_width = 75, + shape = gears.shape.rounded_bar, + shape_border_width = 1, + shape_border_color = beautiful.bg_highlight, + widget = wibox.container.background + }, + layout = wibox.layout.align.horizontal + } + +--DOC_NEWLINE + + -- We don't want to have that bar all the time, only when there is content. + naughty.connect_signal("property::active", function() + notif_wb.visible = #naughty.active > 0 + end) + + +--DOC_HIDE The delayed make sure the legacy popup gets disabled in time +gears.timer.run_delayed_calls_now()--DOC_HIDE + +for i=1, 3 do --DOC_HIDE + naughty.notification { --DOC_HIDE + title = "A notification "..i, --DOC_HIDE + text = "Be notified! "..i, --DOC_HIDE + icon = i%2 == 1 and beautiful.awesome_icon, --DOC_HIDE + timeout = 999, --DOC_HIDE + actions = { --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Accept "..i, --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Refuse", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + } --DOC_HIDE + } --DOC_HIDE +end --DOC_HIDE + + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/notification/shape.lua b/tests/examples/awful/notification/shape.lua new file mode 100644 index 000000000..8b48110e2 --- /dev/null +++ b/tests/examples/awful/notification/shape.lua @@ -0,0 +1,42 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE +require("_default_look") --DOC_HIDE +local gears = {shape = require("gears.shape")} --DOC_HIDE +local awful = {wibar = require("awful.wibar")} --DOC_HIDE +local naughty = require("naughty") --DOC_HIDE + +screen[1]._resize {width = 640, height = 240} --DOC_HIDE + +local some_wibar = awful.wibar {position = "bottom", height = 48, visible = true} --DOC_HIDE + +--DOC_NEWLINE + + -- A notification popup using the default widget_template. + naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} + end) + +--DOC_NEWLINE + + -- Notifications as widgets for any `wibox`/`awful.wibar`/`awful.popup` + some_wibar.widget = naughty.list.notifications {} + +--DOC_NEWLINE + + local shapes = { + gears.shape.octogon, + gears.shape.rounded_rect, + gears.shape.rounded_bar + } + +--DOC_NEWLINE + + for idx=1, 3 do + naughty.notification { + title = "A notification", + border_color = "#0000ff", + border_width = idx*2, + shape = shapes[idx], + } + end + +require("gears.timer").run_delayed_calls_now() diff --git a/tests/examples/awful/popup/wiboxtypes.lua b/tests/examples/awful/popup/wiboxtypes.lua index 02e851cba..c6fa28d9d 100644 --- a/tests/examples/awful/popup/wiboxtypes.lua +++ b/tests/examples/awful/popup/wiboxtypes.lua @@ -5,6 +5,7 @@ require("_date") local awful = require("awful") local gears = require("gears") +local naughty = require("naughty") local wibox = require("wibox") local beautiful = require("beautiful") --DOC_HIDE local look = require("_default_look") @@ -136,6 +137,7 @@ local function create_info(text, x, y, width, height) text = text, align = "center", ellipsize = "none", + wrap = "word", widget = wibox.widget.textbox }, margins = 10, @@ -170,17 +172,29 @@ local function create_line(x1, y1, x2, y2) }, {x=x1, y=y1}) end -create_info("awful.wibar", 200, 50, 100, 30) +naughty.connect_signal("request::display", function(n) + naughty.layout.box {notification = n} +end) + +naughty.notification { + title = "A notification", + message = "With a message! ....", + position = "top_middle", +} + +create_info("awful.wibar", 100, 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("naughty.layout.box", 255, 110, 130, 30) create_info("Standard `wibox`", 420, 420, 130, 30) -create_line(250, 10, 250, 55) +create_line(150, 10, 150, 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) +create_line(320, 60, 320, 110) --DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/naughty/actions.lua b/tests/examples/naughty/actions.lua index f21d73ef2..34cd8fd43 100644 --- a/tests/examples/naughty/actions.lua +++ b/tests/examples/naughty/actions.lua @@ -1,5 +1,6 @@ --DOC_NO_USAGE --DOC_HIDE_ALL +--DOC_GEN_IMAGE -- local naughty = require("naughty") dbus.notify_send( diff --git a/tests/examples/naughty/colors.lua b/tests/examples/naughty/colors.lua index 2149e4297..3a050ed29 100644 --- a/tests/examples/naughty/colors.lua +++ b/tests/examples/naughty/colors.lua @@ -1,3 +1,4 @@ +--DOC_GEN_IMAGE local beautiful = require("beautiful") --DOC_HIDE @@ -5,7 +6,9 @@ local text = [[An important notification ]] -require("naughty").notify { +--DOC_NEWLINE + +require("naughty").notification { title = "Hello world!", text = text, icon = beautiful.icon, diff --git a/tests/examples/naughty/helloworld.lua b/tests/examples/naughty/helloworld.lua index 5c6daecce..83eb95739 100644 --- a/tests/examples/naughty/helloworld.lua +++ b/tests/examples/naughty/helloworld.lua @@ -1,4 +1,5 @@ --DOC_HIDE_ALL +--DOC_GEN_IMAGE -- local naughty = require("naughty") dbus.notify_send( diff --git a/tests/examples/naughty/shape.lua b/tests/examples/naughty/shape.lua index b685b605c..e1a27657a 100644 --- a/tests/examples/naughty/shape.lua +++ b/tests/examples/naughty/shape.lua @@ -1,31 +1,36 @@ +--DOC_GEN_IMAGE --DOC_NO_USAGE local beautiful = require("beautiful") --DOC_HIDE local gears = {shape=require("gears.shape")} --DOC_HIDE local naughty = require("naughty") --DOC_HIDE -local text = [[An important -notification -]] + local text = [[An important + notification + ]] -local shapes = { - gears.shape.rounded_rect, - gears.shape.hexagon, - gears.shape.octogon, - function(cr, w, h) - return gears.shape.infobubble(cr, w, h, 20, 10, w/2 - 10) - end -} +--DOC_NEWLINE -for _, s in ipairs(shapes) do - naughty.notify { - title = "Hello world!", - text = text, - icon = beautiful.icon, - shape = s, - border_width = 3, - border_color = beautiful.bg_highlight, - margin = 15, - } -end + local shapes = { + gears.shape.rounded_rect, + gears.shape.hexagon, + gears.shape.octogon, + function(cr, w, h) + return gears.shape.infobubble(cr, w, h, 20, 10, w/2 - 10) + end + } + +--DOC_NEWLINE + + for _, s in ipairs(shapes) do + naughty.notify { + title = "Hello world!", + text = text, + icon = beautiful.icon, + shape = s, + border_width = 3, + border_color = beautiful.bg_highlight, + margin = 15, + } + end --DOC_HIDE vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 diff --git a/tests/examples/naughty/template.lua b/tests/examples/naughty/template.lua index ca51770dd..92fd5572d 100644 --- a/tests/examples/naughty/template.lua +++ b/tests/examples/naughty/template.lua @@ -1,3 +1,4 @@ +--DOC_GEN_IMAGE local file_path, image_path = ... require("_common_template")(...) local wibox = require("wibox") diff --git a/tests/examples/wibox/nwidget/actionlist/fancy.lua b/tests/examples/wibox/nwidget/actionlist/fancy.lua new file mode 100644 index 000000000..e607b5e65 --- /dev/null +++ b/tests/examples/wibox/nwidget/actionlist/fancy.lua @@ -0,0 +1,62 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE --DOC_NO_USAGE +local naughty = { --DOC_HIDE + list = {actions = require("naughty.list.actions")}, --DOC_HIDE + notification = require("naughty.notification"), --DOC_HIDE + action = require("naughty.action") --DOC_HIDE +} --DOC_HIDE +local gears = {shape = require("gears.shape")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { --DOC_HIDE + title = "A notification", --DOC_HIDE + message = "This notification has actions!", --DOC_HIDE + actions = { --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Accept", --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Refuse", --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Ignore", --DOC_HIDE + }, --DOC_HIDE + } --DOC_HIDE + } --DOC_HIDE + +--DOC_NEWLINE + +parent:add( wibox.container.background(--DOC_HIDE + wibox.widget { + notification = notif, + base_layout = wibox.widget { + spacing = 3, + spacing_widget = wibox.widget { + orientation = "horizontal", + widget = wibox.widget.separator, + }, + layout = wibox.layout.fixed.vertical + }, + widget_template = { + { + { + { + id = "text_role", + widget = wibox.widget.textbox + }, + widget = wibox.container.place + }, + shape = gears.shape.rounded_rect, + shape_border_width = 2, + shape_border_color = beautiful.bg_normal, + forced_height = 30, + widget = wibox.container.background, + }, + margins = 4, + widget = wibox.container.margin, + }, + forced_width = 100, --DOC_HIDE + widget = naughty.list.actions, + } +,beautiful.bg_normal)) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/actionlist/fancy_icons.lua b/tests/examples/wibox/nwidget/actionlist/fancy_icons.lua new file mode 100644 index 000000000..d341a3cdf --- /dev/null +++ b/tests/examples/wibox/nwidget/actionlist/fancy_icons.lua @@ -0,0 +1,68 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE --DOC_NO_USAGE +local naughty = { --DOC_HIDE + list = {actions = require("naughty.list.actions")}, --DOC_HIDE + notification = require("naughty.notification"), --DOC_HIDE + action = require("naughty.action") --DOC_HIDE +} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { --DOC_HIDE + title = "A notification", --DOC_HIDE + message = "This notification has actions!", --DOC_HIDE + actions = { --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Accept", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Refuse", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Ignore", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + } --DOC_HIDE + } --DOC_HIDE + +--DOC_NEWLINE + +parent:add( wibox.container.background(--DOC_HIDE + wibox.widget { + notification = notif, + forced_width = 250, --DOC_HIDE + base_layout = wibox.widget { + spacing = 3, + spacing_widget = wibox.widget { + orientation = "vertical", + widget = wibox.widget.separator, + }, + layout = wibox.layout.flex.horizontal + }, + widget_template = { + { + { + { + id = "icon_role", + forced_height = 16, + forced_width = 16, + widget = wibox.widget.imagebox + }, + { + id = "text_role", + widget = wibox.widget.textbox + }, + spacing = 5, + layout = wibox.layout.fixed.horizontal + }, + id = "background_role", + widget = wibox.container.background, + }, + margins = 4, + widget = wibox.container.margin, + }, + widget = naughty.list.actions, + } +,beautiful.bg_normal)) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/actionlist/simple.lua b/tests/examples/wibox/nwidget/actionlist/simple.lua new file mode 100644 index 000000000..672308a96 --- /dev/null +++ b/tests/examples/wibox/nwidget/actionlist/simple.lua @@ -0,0 +1,34 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE --DOC_NO_USAGE +local naughty = { --DOC_HIDE + list = {actions = require("naughty.list.actions")}, --DOC_HIDE + notification = require("naughty.notification"), --DOC_HIDE + action = require("naughty.action") --DOC_HIDE +} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { + title = "A notification", + message = "This notification has actions!", + actions = { + naughty.action { + name = "Accept", + }, + naughty.action { + name = "Refuse", + }, + naughty.action { + name = "Ignore", + }, + } + } + +--DOC_NEWLINE + +parent:add( wibox.container.background(--DOC_HIDE + wibox.widget { + notification = notif, + widget = naughty.list.actions, + } +,beautiful.bg_normal)) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/actionlist/style.lua b/tests/examples/wibox/nwidget/actionlist/style.lua new file mode 100644 index 000000000..c5cbdd1af --- /dev/null +++ b/tests/examples/wibox/nwidget/actionlist/style.lua @@ -0,0 +1,63 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE --DOC_NO_USAGE +local naughty = { --DOC_HIDE + list = {actions = require("naughty.list.actions")}, --DOC_HIDE + notification = require("naughty.notification"), --DOC_HIDE + action = require("naughty.action") --DOC_HIDE +} --DOC_HIDE +local gears = {shape = require("gears.shape")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { --DOC_HIDE + title = "A notification", --DOC_HIDE + message = "This notification has actions!", --DOC_HIDE + actions = { --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Accept", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Refuse", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + selected = true, --DOC_HIDE + }, --DOC_HIDE + naughty.action { --DOC_HIDE + name = "Ignore", --DOC_HIDE + icon = beautiful.awesome_icon, --DOC_HIDE + }, --DOC_HIDE + } --DOC_HIDE + } --DOC_HIDE + +--DOC_NEWLINE + +parent:add( wibox.container.margin(--DOC_HIDE + wibox.widget { + notification = notif, + forced_width = 250, --DOC_HIDE + base_layout = wibox.widget { + spacing = 3, + spacing_widget = wibox.widget { + orientation = "vertical", + widget = wibox.widget.separator, + }, + layout = wibox.layout.flex.horizontal + }, + style = { + underline_normal = false, + underline_selected = true, + shape_normal = gears.shape.octogon, + shape_selected = gears.shape.hexagon, + shape_border_width_normal = 2, + shape_border_width_selected = 4, + icon_size_normal = 16, + icon_size_selected = 24, + shape_border_color_normal = "#0000ff", + shape_border_color_selected = "#ff0000", + bg_normal = "#ffff00", + bg_selected = "#00ff00", + }, + forced_height = beautiful.get_font_height(beautiful.font) * 2.5, + widget = naughty.list.actions, + } +,0,0,5,5)) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/default.lua b/tests/examples/wibox/nwidget/default.lua new file mode 100644 index 000000000..3f670c2ce --- /dev/null +++ b/tests/examples/wibox/nwidget/default.lua @@ -0,0 +1,107 @@ +--DOC_GEN_IMAGE --DOC_HIDE_ALL +local parent = ... +local naughty = require("naughty") +local wibox = require("wibox") +local beautiful = require("beautiful") +local def = require("naughty.widget._default") +local acommon = require("awful.widget.common") +local aplace = require("awful.placement") +local gears = require("gears") + +beautiful.notification_bg = beautiful.bg_normal + +local notif = naughty.notification { + title = "A notification", + message = "This notification has actions!", + icon = beautiful.awesome_icon, + actions = { + naughty.action { + name = "Accept", + icon = beautiful.awesome_icon, + }, + naughty.action { + name = "Refuse", + icon = beautiful.awesome_icon, + }, + naughty.action { + name = "Ignore", + icon = beautiful.awesome_icon, + }, + } +} + +local default = wibox.widget(def) + +acommon._set_common_property(default, "notification", notif) + +local w, h = default:fit({dpi=96}, 9999, 9999) +default.forced_width = w + 25 +default.forced_height = h + +local canvas = wibox.layout.manual() +canvas.forced_width = w + 150 +canvas.forced_height = h + 100 + +canvas:add_at(default, aplace.centered) + +local function create_info(text, x, y, width, height) + canvas:add_at(wibox.widget { + { + { + text = text, + align = "center", + ellipsize = "none", + wrap = "word", + widget = wibox.widget.textbox + }, + top = 2, + bottom = 2, + left = 10, + right = 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("naughty.widget.background", 10, canvas.forced_height - 30, nil, nil) +create_line(80, canvas.forced_height-55, 80, canvas.forced_height - 30) + +create_info("naughty.list.actions", 170, canvas.forced_height - 30, nil, nil) +create_line(200, canvas.forced_height-105, 200, canvas.forced_height - 30) + +create_info("naughty.widget.icon", 20, 25, nil, nil) +create_line(80, 40, 80, 60) + +create_info("naughty.widget.title", 90, 4, nil, nil) +create_line(140, 20, 140, 60) + +create_info("naughty.widget.message", 150, 25, nil, nil) +create_line(210, 40, 210, 75) + +parent:add(canvas) diff --git a/tests/examples/wibox/nwidget/icon/simple.lua b/tests/examples/wibox/nwidget/icon/simple.lua new file mode 100644 index 000000000..50cdac9ad --- /dev/null +++ b/tests/examples/wibox/nwidget/icon/simple.lua @@ -0,0 +1,22 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE +local naughty = { --DOC_HIDE + widget = { icon = require("naughty.widget.icon")}, --DOC_HIDE + notification = require("naughty.notification")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { + title = "A notification", + message = "This notification has actions!", + icon = beautiful.awesome_icon, + } + +--DOC_NEWLINE + +parent:add( --DOC_HIDE + wibox.widget { + notification = notif, + widget = naughty.widget.icon, + } +) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/icon/strategy.lua b/tests/examples/wibox/nwidget/icon/strategy.lua new file mode 100644 index 000000000..f7e2563fe --- /dev/null +++ b/tests/examples/wibox/nwidget/icon/strategy.lua @@ -0,0 +1,62 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE_ALL +local naughty = { + widget = { icon = require("naughty.widget.icon")}, + notification = require("naughty.notification"), +} +local wibox = require("wibox") +local beautiful = require("beautiful") + +local notif = naughty.notification { + title = "A notification", + text = "This notification has actions!", + icon = beautiful.awesome_icon, + actions = { + ["Accept"] = function() end, + ["Refuse"] = function() end, + ["Ignore"] = function() end, + } +} + +local icons = {} + +for _, strategy in ipairs {"resize", "scale", "center" } do + table.insert(icons, wibox.widget { + { + { + resize_strategy = strategy, + notification = notif, + widget = naughty.widget.icon, + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }, + valign = "top", + halign = "left", + widget = wibox.container.place + }) +end + +parent:add( + wibox.widget { + { + markup = "resize:", + widget = wibox.widget.textbox, + }, + { + markup = "scale:", + widget = wibox.widget.textbox, + }, + { + markup = "center:", + widget = wibox.widget.textbox, + }, + icons[1], + icons[2], + icons[3], + forced_num_rows = 2, + forced_num_cols = 3, + spacing = 5, + widget = wibox.layout.grid, + } +) diff --git a/tests/examples/wibox/nwidget/message/simple.lua b/tests/examples/wibox/nwidget/message/simple.lua new file mode 100644 index 000000000..fd320e57d --- /dev/null +++ b/tests/examples/wibox/nwidget/message/simple.lua @@ -0,0 +1,22 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE +local naughty = { --DOC_HIDE + widget = { message = require("naughty.widget.message")}, --DOC_HIDE + notification = require("naughty.notification")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { + title = "A notification", + message = "This notification no actions!", + icon = beautiful.awesome_icon, + } + +--DOC_NEWLINE + +parent:add( --DOC_HIDE + wibox.widget { + notification = notif, + widget = naughty.widget.message, + } +) --DOC_HIDE diff --git a/tests/examples/wibox/nwidget/title/simple.lua b/tests/examples/wibox/nwidget/title/simple.lua new file mode 100644 index 000000000..4553041a9 --- /dev/null +++ b/tests/examples/wibox/nwidget/title/simple.lua @@ -0,0 +1,22 @@ +--DOC_GEN_IMAGE +local parent = ... --DOC_HIDE +local naughty = { --DOC_HIDE + widget = { title = require("naughty.widget.title")}, --DOC_HIDE + notification = require("naughty.notification")} --DOC_HIDE +local wibox = require("wibox") --DOC_HIDE +local beautiful = require("beautiful") --DOC_HIDE + + local notif = naughty.notification { + title = "A notification", + message = "This notification no actions!", + icon = beautiful.awesome_icon, + } + +--DOC_NEWLINE + +parent:add( --DOC_HIDE + wibox.widget { + notification = notif, + widget = naughty.widget.title, + } +) --DOC_HIDE