about summary refs log tree commit diff stats
path: root/client.c
diff options
context:
space:
mode:
Diffstat (limited to 'client.c')
-rw-r--r--client.c55
1 files changed, 33 insertions, 22 deletions
diff --git a/client.c b/client.c
index 6777473..679e2be 100644
--- a/client.c
+++ b/client.c
@@ -11,7 +11,9 @@
 
 #include "wm.h"
 
-void (*arrange)(void *aux);
+static void floating(void);
+static void tiling(void);
+static void (*arrange)(void) = tiling;
 
 void
 max(void *aux)
@@ -26,25 +28,23 @@ max(void *aux)
 	discard_events(EnterWindowMask);
 }
 
-void
-floating(void *aux)
+static void
+floating(void)
 {
 	Client *c;
 
-	arrange = floating;
 	for(c = stack; c; c = c->snext)
 		resize(c);
 	discard_events(EnterWindowMask);
 }
 
-void
-grid(void *aux)
+static void
+tiling(void)
 {
 	Client *c;
 	int n, cols, rows, gw, gh, i, j;
     float rt, fd;
 
-	arrange = grid;
 	if(!clients)
 		return;
 	for(n = 0, c = clients; c; c = c->next, n++);
@@ -76,6 +76,17 @@ grid(void *aux)
 }
 
 void
+toggle(void *aux)
+{
+	if(arrange == floating)
+		arrange = tiling;
+	else
+		arrange = floating;
+	arrange();
+}
+
+
+void
 sel(void *aux)
 {
 	const char *arg = aux;
@@ -114,8 +125,8 @@ resize_title(Client *c)
 	c->tw = 0;
 	for(i = 0; i < TLast; i++)
 		if(c->tags[i])
-			c->tw += textw(&brush.font, c->tags[i]) + brush.font.height;
-	c->tw += textw(&brush.font, c->name) + brush.font.height;
+			c->tw += textw(&dc.font, c->tags[i]) + dc.font.height;
+	c->tw += textw(&dc.font, c->name) + dc.font.height;
 	if(c->tw > c->w)
 		c->tw = c->w + 2;
 	c->tx = c->x + c->w - c->tw + 2;
@@ -240,7 +251,7 @@ manage(Window w, XWindowAttributes *wa)
 	c->border = 1;
 	update_size(c);
 	XSetWindowBorderWidth(dpy, c->win, 1);
-	XSetWindowBorder(dpy, c->win, brush.border);
+	XSetWindowBorder(dpy, c->win, dc.border);
 	XSelectInput(dpy, c->win,
 			StructureNotifyMask | PropertyChangeMask | EnterWindowMask);
 	XGetTransientForHint(dpy, c->win, &c->trans);
@@ -266,7 +277,7 @@ manage(Window w, XWindowAttributes *wa)
 			GrabModeAsync, GrabModeSync, None, None);
 	XGrabButton(dpy, Button3, Mod1Mask, c->win, False, ButtonPressMask,
 			GrabModeAsync, GrabModeSync, None, None);
-	arrange(NULL);
+	arrange();
 	focus(c);
 }
 
@@ -385,7 +396,7 @@ unmanage(Client *c)
 	XFlush(dpy);
 	XSetErrorHandler(error_handler);
 	XUngrabServer(dpy);
-	arrange(NULL);
+	arrange();
 	if(stack)
 		focus(stack);
 }
@@ -417,21 +428,21 @@ draw_client(Client *c)
 	if(c == stack)
 		return;
 
-	brush.x = brush.y = 0;
-	brush.h = c->th;
+	dc.x = dc.y = 0;
+	dc.h = c->th;
 
-	brush.w = 0;
+	dc.w = 0;
 	for(i = 0; i < TLast; i++) {
 		if(c->tags[i]) {
-			brush.x += brush.w;
-			brush.w = textw(&brush.font, c->tags[i]) + brush.font.height;
-			draw(&brush, True, c->tags[i]);
+			dc.x += dc.w;
+			dc.w = textw(&dc.font, c->tags[i]) + dc.font.height;
+			draw(True, c->tags[i]);
 		}
 	}
-	brush.x += brush.w;
-	brush.w = textw(&brush.font, c->name) + brush.font.height;
-	draw(&brush, True, c->name);
-	XCopyArea(dpy, brush.drawable, c->title, brush.gc,
+	dc.x += dc.w;
+	dc.w = textw(&dc.font, c->name) + dc.font.height;
+	draw(True, c->name);
+	XCopyArea(dpy, dc.drawable, c->title, dc.gc,
 			0, 0, c->tw, c->th, 0, 0);
 	XFlush(dpy);
 }
='#n268'>268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
import unicode
import options

import layout/box
import html/tags
import html/dom
import css/values
import utils/twtstr
import io/term

func cells_in(l: CSSLength, state: Viewport, d: int, p: Option[int], o: bool): int =
  return cells(l, d, state.term.width_px, state.term.height_px, p, o)

func cells_w(l: CSSLength, state: Viewport, p: int): int =
  return l.cells_in(state, state.term.ppc, p.some, true)

func cells_h(l: CSSLength, state: Viewport, p: Option[int]): int =
  return l.cells_in(state, state.term.ppl, p, false)

func cells_h(l: CSSLength, state: Viewport, p: int): int =
  return l.cells_in(state, state.term.ppl, p.some, false)

type InlineState = object
  ictx: InlineContext
  skip: bool
  nodes: seq[Node]
  word: InlineWord
  maxwidth: int
  specified: CSSSpecifiedValues

proc newWord(state: var InlineState) =
  let word = InlineWord()
  let specified = state.specified
  word.color = specified{"color"}
  word.fontstyle = specified{"font-style"}
  word.fontweight = specified{"font-weight"}
  word.textdecoration = specified{"text-decoration"}
  word.nodes = state.nodes
  state.word = word

proc finishRow(ictx: InlineContext) =
  if ictx.thisrow.height != 0:
    let oldrow = ictx.thisrow
    ictx.rows.add(oldrow)
    ictx.height += oldrow.height
    ictx.width = max(ictx.width, oldrow.width)
    ictx.thisrow = InlineRow(rely: oldrow.rely + oldrow.height)

proc addAtom(ictx: InlineContext, atom: InlineAtom, maxwidth: int, specified: CSSSpecifiedValues) =
  # Whitespace between words
  var shift = 0
  let whitespacepre = specified{"white-space"} in {WHITESPACE_PRE, WHITESPACE_PRE_WRAP}
  if ictx.whitespace:
    if ictx.thisrow.atoms.len > 0 or whitespacepre:
      shift = 1
    ictx.whitespace = false

  # Line wrapping
  if specified{"white-space"} notin {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
    if specified{"word-break"} == WORD_BREAK_NORMAL and ictx.thisrow.width + atom.width + shift > maxwidth:
      ictx.finishRow()
      if not whitespacepre:
        # No whitespace on newline
        shift = 0

  ictx.thisrow.width += shift

  if atom.width > 0:
    atom.relx += ictx.thisrow.width
    ictx.thisrow.width += atom.width
    ictx.thisrow.height = max(ictx.thisrow.height, atom.height)
    ictx.thisrow.atoms.add(atom)

proc addWord(state: var InlineState) =
  if state.word.str != "":
    let row = state.ictx.thisrow
    var word = state.word
    word.height = 1
    state.ictx.addAtom(word, state.maxwidth, state.specified)
    state.newWord()

# Start a new line, even if the previous one is empty
proc flushLine(ictx: InlineContext) =
  ictx.thisrow.height = max(ictx.thisrow.height, 1)
  ictx.finishRow()

proc checkWrap(state: var InlineState, r: Rune) =
  if state.specified{"white-space"} in {WHITESPACE_NOWRAP, WHITESPACE_PRE}:
    return
  case state.specified{"word-break"}
  of WORD_BREAK_BREAK_ALL:
    if state.ictx.thisrow.width + state.word.width + r.width() > state.maxwidth:
      state.addWord()
      state.ictx.finishRow()
  of WORD_BREAK_KEEP_ALL:
    if state.ictx.thisrow.width + state.word.width + r.width() > state.maxwidth:
      state.ictx.finishRow()
  else: discard

proc processWhitespace(state: var InlineState, c: char) =
  state.addWord()
  case state.specified{"white-space"}
  of WHITESPACE_NORMAL, WHITESPACE_NOWRAP:
    state.ictx.whitespace = true
  of WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP:
    if c == '\n':
      state.ictx.flushLine()
    else:
      state.ictx.whitespace = true

proc renderText*(ictx: InlineContext, str: string, maxwidth: int, specified: CSSSpecifiedValues, nodes: seq[Node]) =
  var state: InlineState
  state.specified = specified
  state.ictx = ictx
  state.maxwidth = maxwidth
  state.nodes = nodes
  state.newWord()

  #if str.strip().len > 0:
    #eprint "start", str.strip()
  var i = 0
  while i < str.len:
    var rw = 0
    case str[i]
    of ' ', '\n', '\t':
      state.processWhitespace(str[i])
      inc i
    else:
      var r: Rune
      fastRuneAt(str, i, r)
      rw = r.width()
      state.checkWrap(r)
      state.word.str &= r
      state.word.width += rw

  state.addWord()

proc finish(ictx: InlineContext) =
  ictx.finishRow()

proc computedDimensions(bctx: BlockContext, width: int, height: Option[int]) =
  let pwidth = bctx.specified{"width"}
  if pwidth.auto:
    bctx.compwidth = width
  else:
    bctx.compwidth = pwidth.cells_w(bctx.viewport, width)

  let mlef = bctx.specified{"margin-left"}.cells_w(bctx.viewport, width)
  let mrig = bctx.specified{"margin-right"}.cells_w(bctx.viewport, width)
  bctx.relx = mlef
  bctx.compwidth -= mlef
  bctx.compwidth -= mrig

  let pheight = bctx.specified{"height"}
  if not pheight.auto:
    if pheight.unit != UNIT_PERC or height.issome:
      bctx.compheight = pheight.cells_h(bctx.viewport, height).some

proc newBlockContext_common(parent: BlockContext, box: CSSBox): BlockContext {.inline.} =
  new(result)

  result.viewport = parent.viewport
  result.specified = box.specified
  result.computedDimensions(parent.compwidth, parent.compheight)

proc newBlockContext(parent: BlockContext, box: BlockBox): BlockContext =
  result = newBlockContext_common(parent, box)

proc newInlineBlockContext(parent: BlockContext, box: InlineBlockBox): BlockContext =
  newBlockContext_common(parent, box)

# Anonymous block box.
proc newBlockContext(parent: BlockContext): BlockContext =
  new(result)
  result.specified = parent.specified.inheritProperties()
  result.viewport = parent.viewport
  result.computedDimensions(parent.compwidth, parent.compheight)

# Anonymous block box (root).
proc newBlockContext(viewport: Viewport): BlockContext =
  new(result)
  result.specified = rootProperties()
  result.viewport = viewport
  result.computedDimensions(viewport.term.width, none(int))

proc newInlineContext(bctx: BlockContext): InlineContext =
  new(result)
  result.thisrow = InlineRow()
  bctx.inline = result

# Blocks' positions do not have to be arranged if alignBlocks is called with
# children, whence the separate procedure.
proc arrangeBlocks(bctx: BlockContext) =
  var y = 0
  var margin_todo = 0

  template apply_child(child: BlockContext) =
    child.rely = y
    y += child.height
    bctx.height += child.height
    bctx.width = max(bctx.width, child.width)
    margin_todo = child.margin_bottom

  var i = 0
  if i < bctx.nested.len:
    let child = bctx.nested[i]

    bctx.margin_top = child.margin_top
    let mtop = bctx.specified{"margin-top"}.cells_h(bctx.viewport, bctx.compwidth)
    if mtop > bctx.margin_top or mtop < 0:
      bctx.margin_top = mtop - bctx.margin_top

    apply_child(child)
    inc i

  while i < bctx.nested.len:
    let child = bctx.nested[i]

    if child.margin_top > margin_todo or child.margin_top < 0:
      margin_todo += child.margin_top - margin_todo
    y += margin_todo
    bctx.height += margin_todo

    apply_child(child)
    inc i

  bctx.margin_bottom = margin_todo
  let mbot = bctx.specified{"margin-bottom"}.cells_h(bctx.viewport, bctx.compwidth)
  if mbot > bctx.margin_bottom or mbot < 0:
    bctx.margin_bottom = mbot - bctx.margin_bottom

  if bctx.compheight.issome:
    bctx.height = bctx.compheight.get

proc alignBlock(box: BlockBox)

proc alignInlineBlock(bctx: BlockContext, box: InlineBlockBox, parentcss: CSSSpecifiedValues) =
  box.bctx = bctx.newInlineBlockContext(box)
  alignBlock(box)
  box.bctx.rely += box.bctx.margin_top
  box.bctx.height += box.bctx.margin_top
  box.bctx.height += box.bctx.margin_bottom
  box.ictx.addAtom(box.bctx, bctx.compwidth, parentcss)
  box.ictx.whitespace = false

proc alignInline(bctx: BlockContext, box: InlineBox) =
  if box.node != nil:
    bctx.viewport.nodes.add(box.node)

  let box = InlineBox(box)
  assert box.ictx != nil
  if box.newline:
    box.ictx.flushLine()
  for text in box.text:
    assert box.children.len == 0
    box.ictx.renderText(text, bctx.compwidth, box.specified, bctx.viewport.nodes)

  for child in box.children:
    case child.t
    of DISPLAY_INLINE:
      let child = InlineBox(child)
      child.ictx = box.ictx
      bctx.alignInline(child)
    of DISPLAY_INLINE_BLOCK:
      let child = InlineBlockBox(child)
      child.ictx = box.ictx
      bctx.alignInlineBlock(child, box.specified)
    else:
      assert false, "child.t is " & $child.t
  if box.node != nil:
    discard bctx.viewport.nodes.pop()

proc alignInlines(bctx: BlockContext, inlines: seq[CSSBox]) =
  let ictx = bctx.newInlineContext()
  for child in inlines:
    case child.t
    of DISPLAY_INLINE:
      let child = InlineBox(child)
      child.ictx = ictx
      bctx.alignInline(child)
    of DISPLAY_INLINE_BLOCK:
      let child = InlineBlockBox(child)
      child.ictx = ictx
      bctx.alignInlineBlock(child, bctx.specified)
    else:
      assert false, "child.t is " & $child.t
  ictx.finish()
  bctx.height += ictx.height
  if bctx.compheight.issome:
    bctx.height = bctx.compheight.get
  bctx.width = max(ictx.width, ictx.width)
  bctx.margin_top = bctx.specified{"margin-top"}.cells_h(bctx.viewport, bctx.compwidth)
  bctx.margin_bottom = bctx.specified{"margin-bottom"}.cells_h(bctx.viewport, bctx.compwidth)

proc alignBlocks(bctx: BlockContext, blocks: seq[CSSBox]) =
  # Box contains block boxes.
  # If present, group inline boxes together in anonymous block boxes. Place
  # block boxes inbetween these.
  var blockgroup: seq[CSSBox]
  var has_noinline = false

  template flush_group() =
    if blockgroup.len > 0:
      let gctx = newBlockContext(bctx)
      gctx.alignInlines(blockgroup)
      blockgroup.setLen(0)
      bctx.nested.add(gctx)

  for child in blocks:
    case child.t
    of DISPLAY_BLOCK, DISPLAY_LIST_ITEM:
      let child = BlockBox(child)
      flush_group()
      bctx.nested.add(child.bctx)
      alignBlock(child)
    of DISPLAY_INLINE:
      if child.inlinelayout:
        blockgroup.add(child)
      else:
        flush_group()
        bctx.alignBlocks(child.children)
        #eprint "put"
    of DISPLAY_INLINE_BLOCK:
      blockgroup.add(child)
    else: discard #TODO
  flush_group()

proc alignBlock(box: BlockBox) =
  if box.bctx.done:
    return
  box.bctx.done = true
  if box.node != nil:
    box.bctx.viewport.nodes.add(box.node)
  if box.inlinelayout:
    # Box only contains inline boxes.
    box.bctx.alignInlines(box.children)
  else:
    box.bctx.alignBlocks(box.children)
    box.bctx.arrangeBlocks()
  if box.node != nil:
    discard box.bctx.viewport.nodes.pop()

proc getBox(specified: CSSSpecifiedValues): CSSBox =
  case specified{"display"}
  of DISPLAY_BLOCK:
    result = BlockBox()
  of DISPLAY_INLINE_BLOCK:
    result = InlineBlockBox()
  of DISPLAY_INLINE:
    result = InlineBox()
  of DISPLAY_LIST_ITEM:
    result = ListItemBox()
  of DISPLAY_NONE: return nil
  else: return nil
  result.t = specified{"display"}
  result.specified = specified

# Returns a block box, disregarding the specified value
proc getBlockBox(specified: CSSSpecifiedValues): BlockBox =
  new(result)
  result.t = DISPLAY_BLOCK
  result.specified = specified.copyProperties()
  result.specified{"display"} = DISPLAY_BLOCK

proc getTextBox(box: CSSBox): InlineBox =
  new(result)
  result.t = DISPLAY_INLINE
  result.inlinelayout = true
  result.specified = box.specified.inheritProperties()

proc getPseudoBox(bctx: BlockContext, specified: CSSSpecifiedValues): CSSBox =
  let box = getBox(specified)

  if box == nil:
    return nil

  if specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
    let box = BlockBox(box)
    box.bctx = bctx.newBlockContext(box)

  box.inlinelayout = true
  if specified{"content"}.len > 0:
    let content = getTextBox(box)
    content.text.add($specified{"content"})
    box.children.add(content)
  return box

proc generateBox(elem: Element, viewport: Viewport, bctx: BlockContext = nil): CSSBox =
  elem.rendered = true
  if viewport.map[elem.uid] != nil:
    let box = viewport.map[elem.uid]
    var bctx = bctx
    if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
      let box = BlockBox(box)
      if bctx == nil:
        box.bctx = viewport.newBlockContext()
      else:
        box.bctx = bctx.newBlockContext(box)
      bctx = box.bctx

    var i = 0
    while i < box.children.len:
      let child = box.children[i]
      if child.element != nil:
        box.children[i] = generateBox(child.element, viewport, bctx)
      inc i
    return viewport.map[elem.uid]

  let box = if bctx != nil:
    getBox(elem.css)
  else:
    getBlockBox(elem.css)

  if box == nil:
    return nil

  box.node = elem
  box.element = elem

  var bctx = bctx
  if box.specified{"display"} in {DISPLAY_BLOCK, DISPLAY_LIST_ITEM}:
    let box = BlockBox(box)
    if bctx == nil:
      box.bctx = viewport.newBlockContext()
    else:
      box.bctx = bctx.newBlockContext(box)
    bctx = box.bctx

  var ibox: InlineBox
  template add_ibox() =
    if ibox != nil:
      box.children.add(ibox)
      ibox = nil

  template add_box(child: CSSBox) =
    box.children.add(child)
    if child.t notin {DISPLAY_INLINE, DISPLAY_INLINE_BLOCK} or not child.inlinelayout:
      box.inlinelayout = false

  box.inlinelayout = true

  if box.t == DISPLAY_LIST_ITEM:
    var ordinalvalue = 1
    if elem.tagType == TAG_LI:
      ordinalvalue = HTMLLIElement(elem).ordinalvalue
    let marker = box.getTextBox()
    marker.text.add(elem.css{"list-style-type"}.listMarker(ordinalvalue))
    add_box(marker)

  for child in elem.childNodes:
    case child.nodeType
    of ELEMENT_NODE:
      let elem = Element(child)
      if elem.tagType == TAG_BR:
        add_ibox()
        ibox = box.getTextBox()
        ibox.newline = true

      let before = elem.pseudo[PSEUDO_BEFORE]
      if before != nil:
        let bbox = bctx.getPseudoBox(before)
        bbox.node = elem
        if bbox != nil:
          add_box(bbox)

      let cbox = elem.generateBox(viewport, bctx)
      if cbox != nil:
        add_ibox()
        add_box(cbox)

      let after = elem.pseudo[PSEUDO_AFTER]
      if after != nil:
        let abox = bctx.getPseudoBox(after)
        if abox != nil:
          add_box(abox)
    of TEXT_NODE:
      let text = Text(child)
      # Don't generate empty anonymous inline blocks between block boxes
      if box.specified{"display"} == DISPLAY_INLINE or
          box.children.len > 0 and box.children[^1].specified{"display"} == DISPLAY_INLINE or
          box.specified{"white-space"} in {WHITESPACE_PRE_LINE, WHITESPACE_PRE, WHITESPACE_PRE_WRAP} or
          not text.data.onlyWhitespace():
        if ibox == nil:
          ibox = box.getTextBox()
        ibox.text.add(text.data)
    else: discard
  add_ibox()

  viewport.map[elem.uid] = box

  return box

proc renderLayout*(viewport: var Viewport, document: Document) =
  if viewport.root == nil or document.all_elements.len != viewport.map.len:
    viewport.map = newSeq[CSSBox](document.all_elements.len)
  else:
    var i = 0
    while i < viewport.map.len:
      if not document.all_elements[i].rendered:
        viewport.map[i] = nil
      inc i
  viewport.root = BlockBox(document.root.generateBox(viewport))
  alignBlock(viewport.root)