1
0
Fork 0
7 Example Shared Helpers
Agent edited this page 2026-05-26 05:47:03 -04:00

Example shared helpers and rules

Several example configs rely on two shared files:

  • examples/lib/common.lua
  • examples/rules.lua

This page explains what those files do, shows the most important code snippets, and explains how to reuse the same pattern in your own config.


How to use these helpers from a config

A typical example file starts like this:

local common = include("lib/common.lua")
local shared_rules = include("rules.lua")
local commands = common.commands

That gives the config access to:

  • reusable focus helpers
  • reusable resize math
  • reusable draw helpers
  • reusable example command strings
  • reusable example rules

The point is not just code reuse. The point is keeping the main config readable.


Why use shared files at all?

Without shared files, every example would need to repeat:

  • focus helper code
  • resize math
  • draw helpers
  • common command strings
  • shared rules

That would make the examples longer and harder to compare.

The shared files make it easier to see what each example is uniquely trying to teach.


examples/lib/common.lua

This file is a shared helper module. It returns a table named M with helper functions and command strings.

Typical use:

local common = include("lib/common.lua")

Then you can call helpers like:

  • common.make_resolve_focus(...)
  • common.resize_bounds(...)
  • common.draw_background
  • common.draw_focus_border
  • common.commands

common.make_resolve_focus(...)

This helper builds a resolve_focus hook for the examples. Its structure starts like this:

function M.make_resolve_focus(options)
  local drag_modifier = options and options.drag_modifier or nil

  return function(ctx)
    if ctx.reason == "window_mapped" and ctx.window and not ctx.window.exclude_from_focus then
      focus_window(ctx.window)
      return
    end

    if ctx.reason == "pointer_button" and ctx.pressed then
      local modifiers = ctx.modifiers or {}

      if ctx.window and not ctx.window.exclude_from_focus then
        focus_window(ctx.window)

        if drag_modifier and modifiers[drag_modifier] and ctx.button == BTN_LEFT then
          evil.window.begin_move(ctx.window.id)
          return
        end

        if drag_modifier and modifiers[drag_modifier] and ctx.button == BTN_RIGHT then
          evil.window.begin_resize(ctx.window.id, resize_edges_for_pointer(ctx.window, ctx.pointer))
          return
        end

        return
      end

      evil.window.clear_focus()
      return
    end
    ...
  end
end

Its default behavior covers: focus new windows on map, focus clicked windows, clear focus on empty-space clicks, pick another focusable window when one disappears, and optionally start interactive move/resize when a drag modifier is set. This lets several examples stay short without becoming vague: one line gives a readable baseline focus policy.

common.resize_bounds(...)

This helper takes:

  • the current window snapshot
  • dx, dy
  • resize edge flags

and returns a new bounds table.

The key logic is:

function M.resize_bounds(window, dx, dy, edges)
  local left = window.x
  local top = window.y
  local right = window.x + window.w
  local bottom = window.y + window.h

  left, right = apply_horizontal_resize(left, right, dx, edges)
  top, bottom = apply_vertical_resize(top, bottom, dy, edges)

  left, right = clamp_resize_width(left, right, edges, 120)
  top, bottom = clamp_resize_height(top, bottom, edges, 80)

  return {
    x = left,
    y = top,
    w = right - left,
    h = bottom - top,
  }
end

It lets example configs resize without repeating the math in every file.

common.draw_background

The background helper eventually delegates to a dot-grid renderer:

function M.draw_background(ctx)
  return M.draw_dot_grid(ctx)
end

Inside that grid renderer, the shapes are created from viewport facts like this:

local screen_x = math.floor(((world_x - visible.x) * viewport.zoom) + 0.5)
local screen_y = math.floor(((world_y - visible.y) * viewport.zoom) + 0.5)
...
shapes[#shapes + 1] = grid_dot_shape(screen_x, screen_y, size, color)

This is the correct draw-hook model: read the viewport, convert world positions to screen coordinates, return shapes, and never mutate runtime state.


common.draw_focus_border

The shared focus-border helper looks like this:

function M.draw_focus_border(ctx)
  local window = M.focused_window(ctx)
  if not window then
    return {}
  end

  return {
    evil.draw.stroke_rect({
      space = "world",
      x = window.x,
      y = window.y,
      w = window.w,
      h = window.h,
      width = 1,
      outer = 4,
      color = { 0.18, 0.15, 0.26, 0.95 },
    }),
    evil.draw.stroke_rect({
      space = "world",
      x = window.x,
      y = window.y,
      w = window.w,
      h = window.h,
      width = 2,
      outer = 2,
      color = { 0.74, 0.58, 0.98, 0.98 },
    }),
  }
end

A draw helper stays readable when it does one clear thing: find the focused window and return shapes around it.


common.commands

The shared command table is:

M.commands = {
  terminal = "./scripts/example-launch.sh terminal",
  launcher = "./scripts/example-launch.sh launcher",
  x11_test = "./scripts/example-launch.sh x11-test",
  screenshot = "./scripts/example-launch.sh screenshot",
  browser = "./scripts/example-launch.sh browser",
  file_manager = "./scripts/example-launch.sh file-manager",
}

Readable names like commands.terminal are easier to scan than repeating long shell command strings.

examples/rules.lua

The shared rules file is intentionally tiny:

return {
  { app_id = "foot", floating = true, size = { w = 900, h = 600 } },
  { title_contains = "scratch", exclude_from_focus = true },
}

These show the basic rule format: app_id matches for floating size, and title_contains matches for focus exclusion.


Why these shared files matter for learning

These files are useful to study because they show how to keep config files readable.

A good config does not need to put every line in one file. It can move:

  • reusable helpers
  • reusable rules
  • shared command strings

into helper modules and keep the main config focused on its actual behavior.