r_layout.c revision b18c2d1e
1/*
2 * Copyright notice...
3 */
4
5#include "ctwm.h"
6
7#include <stdlib.h>
8#include <stdio.h>
9#include <string.h>
10
11#include "r_layout.h"
12#include "r_area_list.h"
13#include "r_area.h"
14#include "util.h"
15
16
17/*
18 * Prototype internal funcs
19 */
20static void _RLayoutFreeNames(RLayout *self);
21static RAreaList *_RLayoutRecenterVertically(const RLayout *self,
22                const RArea *far_area);
23static RAreaList *_RLayoutRecenterHorizontally(const RLayout *self,
24                const RArea *far_area);
25static RAreaList *_RLayoutVerticalIntersect(const RLayout *self,
26                const RArea *area);
27static RAreaList *_RLayoutHorizontalIntersect(const RLayout *self,
28                const RArea *area);
29
30/* Foreach() callbacks used in various lookups */
31static bool _findMonitorByXY(const RArea *cur, void *vdata);
32static bool _findMonitorBottomEdge(const RArea *cur, void *vdata);
33static bool _findMonitorTopEdge(const RArea *cur, void *vdata);
34static bool _findMonitorLeftEdge(const RArea *cur, void *vdata);
35static bool _findMonitorRightEdge(const RArea *cur, void *vdata);
36
37
38
39
40/************************
41 *
42 * First, some funcs for creating and destroying RLayout's in various
43 * ways.
44 *
45 ************************/
46
47
48/**
49 * Create an RLayout for a given set of monitors.
50 *
51 * This stashes up the list of monitors, and precalculates the
52 * horizontal/vertical stripes that compose it.
53 */
54RLayout *
55RLayoutNew(RAreaList *monitors)
56{
57	RLayout *layout = malloc(sizeof(RLayout));
58	if(layout == NULL) {
59		abort();
60	}
61
62	layout->monitors = monitors;
63	layout->horiz = RAreaListHorizontalUnion(monitors);
64	layout->vert = RAreaListVerticalUnion(monitors);
65	layout->names = NULL;
66
67	return layout;
68}
69
70
71/**
72 * Create a copy of an RLayout with given amounts cropped off the sides.
73 * This is used anywhere we need to pretend our display area is smaller
74 * than it actually is (e.g., via the BorderBottom/Top/Left/Right config
75 * params)
76 */
77RLayout *
78RLayoutCopyCropped(const RLayout *self, int left_margin, int right_margin,
79                   int top_margin, int bottom_margin)
80{
81	RAreaList *cropped_monitors = RAreaListCopyCropped(self->monitors,
82	                              left_margin, right_margin,
83	                              top_margin, bottom_margin);
84	if(cropped_monitors == NULL) {
85		return NULL;        // nothing to crop, same layout as passed
86	}
87
88	return RLayoutNew(cropped_monitors);
89}
90
91
92/**
93 * Clean up and free any RLayout.names there might be in an RLayout.
94 */
95static void
96_RLayoutFreeNames(RLayout *self)
97{
98	if(self == NULL) {
99		return;
100	}
101	if(self->names != NULL) {
102		free(self->names);
103		self->names = NULL;
104	}
105}
106
107
108/**
109 * Clean up and free an RLayout.
110 */
111void
112RLayoutFree(RLayout *self)
113{
114	if(self == NULL) {
115		return;
116	}
117
118	RAreaListFree(self->monitors);
119	RAreaListFree(self->horiz);
120	RAreaListFree(self->vert);
121	_RLayoutFreeNames(self);
122	free(self);
123}
124
125
126/**
127 * Set the names for our monitors in an RLayout.  This is only used for
128 * the RLayout that describes our complete monitor layout, which fills in
129 * the RANDR names for each output.
130 */
131RLayout *
132RLayoutSetMonitorsNames(RLayout *self, char **names)
133{
134	_RLayoutFreeNames(self);
135	self->names = names;
136	return self;
137}
138
139
140
141/************************
142 *
143 * Next, a few util funcs for dealing with RArea's that are outside our
144 * RLayout, but we want to find the nearest way to move them inside, then
145 * return a list of which RArea's they'd be intersecting with.
146 *
147 ************************/
148
149
150/**
151 * Given an RArea that doesn't reside in any of the areas in our RLayout,
152 * create a list of maximally-tall RArea slices out of our layout where
153 * it would wind up if we brought it onto the nearest screen edge.  This
154 * yields a RAreaList as tall as the slice[es] the window would touch if
155 * we moved it in.
156 *
157 * If we had the move the window horizontally (it was off-screen to the
158 * right or left), it results in a 1-pixel-wide slice of the right- or
159 * left-most self->vert.
160 *
161 * If we had to move it vertically (it was off to the top or bottom), it
162 * winds up being whatever horizontal intersection with self->vert would
163 * result from the window's x and width, with the full height of the
164 * involved slices.
165 *
166 * This is the vertical-stripe-returning counterpart of
167 * _RLayoutRecenterHorizontally().
168 *
169 * This is called only by \_RLayoutVerticalIntersect() when given an RArea
170 * that doesn't already intersect the RLayout.  Will probably not tell
171 * you something useful if given a far_area that already _does_ intersect
172 * self.
173 *
174 * \param self     Our current monitor layout
175 * \param far_area The area to act on
176 */
177static RAreaList *
178_RLayoutRecenterVertically(const RLayout *self, const RArea *far_area)
179{
180	RArea big = RAreaListBigArea(self->monitors), tmp;
181
182	// We assume far_area is outside of self.  So it's in one of the
183	// three labelled areas:
184	//
185	//  |_V_|   ___ tmp.top
186	//  |   |     |
187	// L|   |R    |
188	//  |___|   ___ tmp.bottom
189	//  | V |
190	//
191	// So we'll create an RArea that's the y and height of big (a giant
192	// rectangle covering all the monitors), so that its intersection
193	// with self->vert will always cover a full vertical stripe.  Then
194	// we'll set its x/width so that it's shifted to be at least
195	// minimally inside big somehow.
196
197	// Where did it wind up?
198	if((far_area->x >= big.x && far_area->x <= RAreaX2(&big))
199	                || (RAreaX2(far_area) >= big.x && RAreaX2(far_area) <= RAreaX2(&big))) {
200		// In one of the V areas.  It's already in a horizontal position
201		// that would fit, so we just keep x/width.
202		tmp = RAreaNew(far_area->x, big.y,
203		               far_area->width, big.height);
204	}
205	else if(RAreaX2(far_area) < big.x) {
206		// Off to the left side in L, so move it over just far enough
207		// that 1 pixel of it protrudes into the left side.
208		tmp = RAreaNew(big.x - far_area->width + 1, big.y,
209		               far_area->width, big.height);
210	}
211	else {
212		// Off to the right side in R, so move it over just far enough
213		// that 1 pixel of it protrudes into the right side.
214		tmp = RAreaNew(RAreaX2(&big), big.y,
215		               far_area->width, big.height);
216	}
217
218	// Then intersect that (full height, at least 1 pixel horizontally
219	// somewhere) with our collection of vertical stripes, to yield an
220	// answer.  If the window was off to the left or right, this will
221	// yield a 1-pixel-wide slice of either the left- or right-most
222	// ->vert of our layout.  If it were off the top of bottom, though,
223	// it'll yield some slice of 1 (or more) of our ->vert's, as wide as
224	// the window itself was.
225	return RAreaListIntersect(self->vert, &tmp);
226
227	// n.b.; _RLayoutRecenterHorizontally() is the counterpart to this
228	// with horizontal slices.  The comments in the two have been written
229	// independently with somewhat different explanatory styles, so if
230	// the description here was confusing, try reading the other one and
231	// transposing.
232}
233
234
235/**
236 * Given an RArea that doesn't reside in any of the areas in our RLayout,
237 * create a list of maximally-wide RArea slices out of our layout where
238 * it would wind up if we brought it onto the nearest screen edge.  This
239 * yields a RAreaList as wide as the slice[es] the window would touch if
240 * we moved it in.
241 *
242 * If we had the move the window vertically (it was off-screen to the top
243 * or bottom), it results in a 1-pixel-wide slice of the top- or
244 * bottom-most self->horiz.
245 *
246 * If we had to move it horizontally (it was off to the left or right),
247 * it winds up being whatever vertical intersection with self->horiz
248 * would result from the window's y and height, with the full width of
249 * the involved slices.
250 *
251 * This is the horizontal-stripe-returning counterpart of
252 * _RLayoutRecenterVertically().
253 *
254 * This is called only by \_RLayoutVerticalIntersect() when given an RArea
255 * that doesn't already intersect the RLayout.  Will probably not tell
256 * you something useful if given a far_area that already _does_ intersect
257 * self.
258 *
259 * \param self     Our current monitor layout
260 * \param far_area The area to act on
261 */
262static RAreaList *
263_RLayoutRecenterHorizontally(const RLayout *self, const RArea *far_area)
264{
265	RArea big = RAreaListBigArea(self->monitors), tmp;
266
267	// far_area is outside self, so it's in one of the 3 labelled areas:
268	//
269	// ___T___
270	//  |   |
271	// H|   |H
272	// _|___|_
273	//    B
274	//
275	// We create an RArea that's the x and width of big, so it always
276	// covers the entire width of any member of ->horiz.  Then we move
277	// the far_area in to the nearest edge to figure the y/height to set.
278
279	if((far_area->y >= big.y && far_area->y <= RAreaY2(&big))
280	                || (RAreaY2(far_area) >= big.y && RAreaY2(far_area) <= RAreaY2(&big))) {
281		// In one of the H areas.  Already in a valid place vertically,
282		// so make a horizontal strip that position/tall.
283		tmp = RAreaNew(big.x, far_area->y,
284		               big.width, far_area->height);
285	}
286	else if(RAreaY2(far_area) < big.y) {
287		// Off the top (T); move it down just far enough that it's bottom
288		// protrudes 1 pixel into the top.
289		tmp = RAreaNew(big.x, big.y - far_area->height + 1,
290		               big.width, far_area->height);
291	}
292	else {
293		// Off the bottom (B); move it up just enough that it's top
294		// protrudes 1 pixel into the bottom.
295		tmp = RAreaNew(big.x, RAreaY2(&big),
296		               big.width, far_area->height);
297	}
298
299	// And intersect that RArea with self->horiz.  This results in a
300	// full-width overlap with 1 pixel at the bottom of the bottom-most,
301	// 1 pixel at the top of the top-most, or 1..(far_area->height)
302	// overlap somewhere.  In that last case (far_area was in H), the
303	// intersection may yield multiple areas.
304	return RAreaListIntersect(self->horiz, &tmp);
305
306	// n.b.; _RLayoutRecenterVertically() is the counterpart to this with
307	// vertical slices.  The comments in the two have been written
308	// independently with somewhat different explanatory styles, so if
309	// the description here was confusing, try reading the other one and
310	// transposing.
311}
312
313
314
315/************************
316 *
317 * Some wrappers called when we need to Insersect an RArea with our
318 * RLayout, but also handle the case (using the above funcs) when the
319 * RArea doesn't Intersect our layout by finding the nearest border we
320 * could shuffle it over.
321 *
322 ************************/
323
324
325/**
326 * Find which vertical regions of our monitor layout a given RArea (often
327 * a window) is in.  If it's completely off the screen, we move it until
328 * it's just over the nearest edge, and return the vertical stripe(s) it
329 * would be in then.
330 *
331 * This function is used only by RLayoutFindTopBottomEdges()
332 */
333static RAreaList *
334_RLayoutVerticalIntersect(const RLayout *self, const RArea *area)
335{
336	RAreaList *mit = RAreaListIntersect(self->vert, area);
337
338	if(mit->len == 0) {
339		// Not on screen.  Move it to just over the nearest edge so it
340		// is, and give the slices it's in then.
341		RAreaListFree(mit);
342		mit = _RLayoutRecenterVertically(self, area);
343	}
344	return mit;
345}
346
347
348/**
349 * Find which horizontal regions of our monitor layout a given RArea
350 * (often a window) is in.  If it's completely off the screen, we move it
351 * until it's just over the nearest edge, and return the horizontal
352 * stripe(s) it would be in then.
353 *
354 * This function is used only by RLayoutFindLeftRightEdges()
355 */
356static RAreaList *
357_RLayoutHorizontalIntersect(const RLayout *self, const RArea *area)
358{
359	RAreaList *mit = RAreaListIntersect(self->horiz, area);
360
361	if(mit->len == 0) {
362		// Not on screen.  Move it to just over the nearest edge so it
363		// is, and give the slices it's in then.
364		RAreaListFree(mit);
365		mit = _RLayoutRecenterHorizontally(self, area);
366	}
367
368	return mit;
369}
370
371
372
373/************************
374 *
375 * Some funcs using the above (layers of) utils to find info about which
376 * stripes of the RLayout an Area appears in.   These are used mostly as
377 * backend utils for figuring various f.*zoom's.
378 *
379 ************************/
380
381
382/**
383 * Figure the position (or nearest practical position) of an area in our
384 * screen layout, and return info about the bottom/top stripes it fits
385 * into.
386 *
387 * Note that the return values (params) are slightly counterintuitive;
388 * top tells you where the top of the lowest stripe that area intersects
389 * with is, and bottom tells you the bottom of the highest.
390 *
391 * This is used as a backend piece of various calculations trying to be
392 * sure something winds up on-screen and when figuring out how to zoom
393 * it.
394 *
395 * \param[in]  self   The monitor layout to work from
396 * \param[in]  area   The area to be fit into the monitors
397 * \param[out] top    The top of the lowest stripe area fits into.
398 * \param[out] bottom The bottom of the highest stripe area fits into.
399 */
400void
401RLayoutFindTopBottomEdges(const RLayout *self, const RArea *area, int *top,
402                          int *bottom)
403{
404	RAreaList *mit = _RLayoutVerticalIntersect(self, area);
405
406	if(top != NULL) {
407		*top = RAreaListMaxY(mit);
408	}
409
410	if(bottom != NULL) {
411		*bottom = RAreaListMinY2(mit);
412	}
413
414	RAreaListFree(mit);
415}
416
417
418/**
419 * Find the bottom of the top stripe of self that area fits into.  A
420 * shortcut to get only the second return value of
421 * RLayoutFindTopBottomEdges().
422 */
423int
424RLayoutFindBottomEdge(const RLayout *self, const RArea *area)
425{
426	int min_y2;
427	RLayoutFindTopBottomEdges(self, area, NULL, &min_y2);
428	return min_y2;
429}
430
431
432/**
433 * Find the top of the bottom stripe of self that area fits into.  A
434 * shortcut to get only the first return value of
435 * RLayoutFindTopBottomEdges().
436 */
437int
438RLayoutFindTopEdge(const RLayout *self, const RArea *area)
439{
440	int max_y;
441	RLayoutFindTopBottomEdges(self, area, &max_y, NULL);
442	return max_y;
443}
444
445
446/**
447 * Figure the position (or nearest practical position) of an area in our
448 * screen layout, and return info about the left/rightmost stripes it fits
449 * into.
450 *
451 * As with RLayoutFindTopBottomEdges(), the return values (params) are
452 * slightly counterintuitive.  left tells you where the left-side of the
453 * right-most stripe that area intersects with is, and right tells you
454 * the right side of the left-most.
455 *
456 * This is used as a backend piece of various calculations trying to be
457 * sure something winds up on-screen and when figuring out how to zoom
458 * it.
459 *
460 * \param[in]  self   The monitor layout to work from
461 * \param[in]  area   The area to be fit into the monitors
462 * \param[out] left   The left edge of the right-most stripe area fits into.
463 * \param[out] right  The right edge of the left-most stripe area fits into.
464 */
465void
466RLayoutFindLeftRightEdges(const RLayout *self, const RArea *area, int *left,
467                          int *right)
468{
469	RAreaList *mit = _RLayoutHorizontalIntersect(self, area);
470
471	if(left != NULL) {
472		*left = RAreaListMaxX(mit);
473	}
474
475	if(right != NULL) {
476		*right = RAreaListMinX2(mit);
477	}
478
479	RAreaListFree(mit);
480}
481
482
483/**
484 * Find the left edge of the right-most stripe of self that area fits
485 * into.  A shortcut to get only the first return value of
486 * RLayoutFindLeftRightEdges().
487 */
488int
489RLayoutFindLeftEdge(const RLayout *self, const RArea *area)
490{
491	int max_x;
492	RLayoutFindLeftRightEdges(self, area, &max_x, NULL);
493	return max_x;
494}
495
496
497/**
498 * Find the right edge of the left-most stripe of self that area fits
499 * into.  A shortcut to get only the second return value of
500 * RLayoutFindLeftRightEdges().
501 */
502int
503RLayoutFindRightEdge(const RLayout *self, const RArea *area)
504{
505	int min_x2;
506	RLayoutFindLeftRightEdges(self, area, NULL, &min_x2);
507	return min_x2;
508}
509
510
511
512/************************
513 *
514 * Lookups to find areas in an RLayout by various means.
515 *
516 ************************/
517
518
519/// Internal structure for callback in RLayoutGetAreaAtXY().
520struct monitor_finder_xy {
521	const RArea *area;
522	int x, y;
523};
524
525/// Callback util for RLayoutGetAreaAtXY().
526static bool
527_findMonitorByXY(const RArea *cur, void *vdata)
528{
529	struct monitor_finder_xy *data = (struct monitor_finder_xy *)vdata;
530
531	if(RAreaContainsXY(cur, data->x, data->y)) {
532		data->area = cur;
533		return true;
534	}
535	return false;
536}
537
538/**
539 * Find the RArea in a RLayout that a given coordinate falls into.  In
540 * practice, the RArea's in self are the monitors of the desktop, so this
541 * answers "Which monitor is this position on?"
542 */
543RArea
544RLayoutGetAreaAtXY(const RLayout *self, int x, int y)
545{
546	struct monitor_finder_xy data = { .area = NULL, .x = x, .y = y };
547
548	RAreaListForeach(self->monitors, _findMonitorByXY, &data);
549
550	return data.area == NULL ? self->monitors->areas[0] : *data.area;
551}
552
553
554/**
555 * Return the index'th RArea in an RLayout, or RAreaInvalid() with an out
556 * of range index.
557 */
558RArea
559RLayoutGetAreaIndex(const RLayout *self, int index)
560{
561	if(index >= self->monitors->len || index < 0) {
562		return RAreaInvalid();
563	}
564
565	return self->monitors->areas[index];
566}
567
568
569/**
570 * Return the RArea in self with the name given by the string of length
571 * len at name.  This is only used in RLayoutXParseGeometry() to parse a
572 * fragment of a larger string, hence the need for len.  It's used to
573 * find the monitor with a given name (RANDR output name).
574 */
575RArea
576RLayoutGetAreaByName(const RLayout *self, const char *name, int len)
577{
578	if(self->names != NULL) {
579		if(len < 0) {
580			len = strlen(name);
581		}
582
583		for(int i = 0; i < self->monitors->len
584		                && self->names[i] != NULL; i++) {
585			if(strncmp(self->names[i], name, len) == 0) {
586				return self->monitors->areas[i];
587			}
588		}
589	}
590
591	return RAreaInvalid();
592}
593
594
595
596/************************
597 *
598 * Now some utils for finding various edges of the monitors a given RArea
599 * intersects with.
600 *
601 ************************/
602
603
604/// Internal struct for use in FindMonitor*Edge() callbacks.
605struct monitor_edge_finder {
606	const RArea *area;
607	union {
608		int max_x;
609		int max_y;
610		int min_x2;
611		int min_y2;
612	} u;
613	bool found;
614};
615
616/// Callback util for RLayoutFindMonitorBottomEdge()
617static bool
618_findMonitorBottomEdge(const RArea *cur, void *vdata)
619{
620	struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata;
621
622	// Does the area we're looking for intersect this piece of the
623	// RLayout, is the bottom of the area shown on it, and is the bottom
624	// of this piece the highest we've yet found that satisfies those
625	// conditions?
626	if(RAreaIsIntersect(cur, data->area)
627	                && RAreaY2(cur) > RAreaY2(data->area)
628	                && (!data->found || RAreaY2(cur) < data->u.min_y2)) {
629		data->u.min_y2 = RAreaY2(cur);
630		data->found = true;
631	}
632	return false;
633}
634
635/**
636 * Find the bottom edge of the top-most monitor that contains the most of
637 * a given RArea.  Generally, the area would be a window.
638 *
639 * That is, we find the monitor whose bottom is the highest up, but that
640 * still shows the bottom edge of the window, and return that monitor's
641 * bottom.  If the bottom of the window is off all the monitors, that's
642 * just the highest-ending monitor that contains the window.
643 */
644int
645RLayoutFindMonitorBottomEdge(const RLayout *self, const RArea *area)
646{
647	struct monitor_edge_finder data = { .area = area };
648
649	RAreaListForeach(self->monitors, _findMonitorBottomEdge, &data);
650
651	return data.found ? data.u.min_y2 : RLayoutFindBottomEdge(self, area);
652}
653
654
655/// Callback util for RLayoutFindMonitorTopEdge()
656static bool
657_findMonitorTopEdge(const RArea *cur, void *vdata)
658{
659	struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata;
660
661	// Does the area we're looking for intersect this piece of the
662	// RLayout, is the top of the area shown on it, and is the top
663	// of this piece the lowest we've yet found that satisfies those
664	// conditions?
665	if(RAreaIsIntersect(cur, data->area)
666	                && cur->y < data->area->y
667	                && (!data->found || cur->y > data->u.max_y)) {
668		data->u.max_y = cur->y;
669		data->found = true;
670	}
671	return false;
672}
673
674/**
675 * Find the top edge of the bottom-most monitor that contains the most of
676 * a given RArea.  Generally, the area would be a window.
677 *
678 * That is, we find the monitor whose top is the lowest down, but that
679 * still shows the top edge of the window, and return that monitor's top.
680 * If the top of the window is off all the monitors, that's just the
681 * lowest-ending monitor that contains part of the window.
682 */
683int
684RLayoutFindMonitorTopEdge(const RLayout *self, const RArea *area)
685{
686	struct monitor_edge_finder data = { .area = area };
687
688	RAreaListForeach(self->monitors, _findMonitorTopEdge, &data);
689
690	return data.found ? data.u.max_y : RLayoutFindTopEdge(self, area);
691}
692
693
694/// Callback util for RLayoutFindMonitorLeftEdge()
695static bool
696_findMonitorLeftEdge(const RArea *cur, void *vdata)
697{
698	struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata;
699
700	// Does the area we're looking for intersect this piece of the
701	// RLayout, is the left of the area shown on it, and is the left of
702	// this piece the right-most we've yet found that satisfies those
703	// conditions?
704	if(RAreaIsIntersect(cur, data->area)
705	                && cur->x < data->area->x
706	                && (!data->found || cur->x > data->u.max_x)) {
707		data->u.max_x = cur->x;
708		data->found = true;
709	}
710	return false;
711}
712
713/**
714 * Find the left edge of the right-most monitor that contains the most of
715 * a given RArea.  Generally, the area would be a window.
716 *
717 * That is, we find the monitor whose left is the furthest right, but
718 * that still shows the left edge of the window, and return that
719 * monitor's left.  If the left edge of the window is off all the
720 * monitors, that's just the right-most-ending monitor that contains the
721 * window.
722 */
723int
724RLayoutFindMonitorLeftEdge(const RLayout *self, const RArea *area)
725{
726	struct monitor_edge_finder data = { .area = area };
727
728	RAreaListForeach(self->monitors, _findMonitorLeftEdge, &data);
729
730	return data.found ? data.u.max_x : RLayoutFindLeftEdge(self, area);
731}
732
733
734/// Callback util for RLayoutFindMonitorRightEdge()
735static bool
736_findMonitorRightEdge(const RArea *cur, void *vdata)
737{
738	struct monitor_edge_finder *data = (struct monitor_edge_finder *)vdata;
739
740	// Does the area we're looking for intersect this piece of the
741	// RLayout, is the right of the area shown on it, and is the right of
742	// this piece the left-most we've yet found that satisfies those
743	// conditions?
744	if(RAreaIsIntersect(cur, data->area)
745	                && RAreaX2(cur) > RAreaX2(data->area)
746	                && (!data->found || RAreaX2(cur) < data->u.min_x2)) {
747		data->u.min_x2 = RAreaX2(cur);
748		data->found = true;
749	}
750	return false;
751}
752
753/**
754 * Find the right edge of the left-most monitor that contains the most of
755 * a given RArea.  Generally, the area would be a window.
756 *
757 * That is, we find the monitor whose right is the furthest left, but
758 * that still shows the right edge of the window, and return that
759 * monitor's right.  If the right edge of the window is off all the
760 * monitors, that's just the left-most-ending monitor that contains the
761 * window.
762 */
763int
764RLayoutFindMonitorRightEdge(const RLayout *self, const RArea *area)
765{
766	struct monitor_edge_finder data = { .area = area };
767
768	RAreaListForeach(self->monitors, _findMonitorRightEdge, &data);
769
770	return data.found ? data.u.min_x2 : RLayoutFindRightEdge(self, area);
771}
772
773
774
775/************************
776 *
777 * Backend funcs called by the f.*zoom handlers to figure the area we
778 * should zoom into.
779 *
780 ************************/
781
782
783/**
784 * Figure the best way to stretch an area across the full horizontal
785 * width of an RLayout.  This is the backend for the f.xhorizoom ctwm
786 * function, zooming a window to the full width of all monitors.
787 */
788RArea
789RLayoutFullHoriz(const RLayout *self, const RArea *area)
790{
791	int max_x, min_x2;
792
793	RLayoutFindLeftRightEdges(self, area, &max_x, &min_x2);
794
795	return RAreaNew(max_x, area->y, min_x2 - max_x + 1, area->height);
796
797	/**
798	 * This yields an area:
799	 * ~~~
800	 * TL   W
801	 *   *-----*
802	 *   |     |
803	 *  H|     |
804	 *   |     |
805	 *   *-----*
806	 * ~~~
807	 *
808	 * The precise construction of the area can be tricky.
809	 *
810	 * In the simplest case, the area is entirely in one horizontal
811	 * stripe to start with.  In that case, max_x is the left side of
812	 * that box, min_x2 is the right side, so the resulting area starts
813	 * at (left margin, area y), with the height of y and the width of
814	 * the whole stripe.  Easy.
815	 *
816	 * When it spans multiple, it's more convoluted.  Let's consider an
817	 * example layout (of horizontal stripes, so that top stripe may be
818	 * across 2 monitors) to make it a little clearer:
819	 *
820	 * ~~~
821	 * *--------------------------*
822	 * |             |......2.....|
823	 * |                          |  <-----.
824	 * |             1 =========  |         .
825	 * *-------------*-=========--*-*        >-- 2 horiz stripes
826	 *               | =========    |       '
827	 *               |  /           |  <---'
828	 *       area  --+-'            |
829	 *               *--------------*
830	 * ~~~
831	 *
832	 * So in this case, we're trying to stretch area out as far
833	 * horizontal as it can go, crossing monitors if possible.
834	 *
835	 * So, the top-left corner of our box (TL) has the X coordinate of
836	 * the right-most strip we started with (the lower), and the Y
837	 * coordinate of the top of the area, yielding point (1) above (not
838	 * the asterisk; specifically where (1) sits).
839	 *
840	 * The width W is the difference between the right of the
841	 * left-most-ending (in this case, the top) stripe, and the left of
842	 * the right-most-starting (the bottom) (plus 1 because math).
843	 * That's the width of the intersecting horizontal area (2) above.
844	 *
845	 * And the height H is just the height of the original area.  And so,
846	 * our resulting area is the height of that original area (in ='s),
847	 * and stretched to the left and right until it runs into one or the
848	 * other monitor edge (1 space to the left, 2 to the right, in our
849	 * diagram).
850	 */
851}
852
853
854/**
855 * Figure the best way to stretch an area across the full vertical height
856 * of an RLayout.  This is the backend for the f.xzoom ctwm function,
857 * zooming a window to the full height of all monitors.
858 */
859RArea
860RLayoutFullVert(const RLayout *self, const RArea *area)
861{
862	int max_y, min_y2;
863
864	RLayoutFindTopBottomEdges(self, area, &max_y, &min_y2);
865
866	return RAreaNew(area->x, max_y, area->width, min_y2 - max_y + 1);
867
868	// X-ref long comment above in RLayoutFullHoriz() for worked example.
869	// This is just rotated 90 degrees, but the logic works out about the
870	// same.
871}
872
873
874/**
875 * Figure the best way to stretch an area across the largest horizontal
876 * and vertical space it can from its current position.  Essentially,
877 * stretch it in all directions until it hits the edge of our available
878 * space.
879 *
880 * This is the backend for the f.xfullzoom function.
881 */
882RArea
883RLayoutFull(const RLayout *self, const RArea *area)
884{
885	RArea full_horiz, full_vert, full1, full2;
886
887	// Get the boxes for full horizontal and vertical zooms, using the
888	// above functions.
889	full_horiz = RLayoutFullHoriz(self, area);
890	full_vert = RLayoutFullVert(self, area);
891
892	// Now stretch each of those in the other direction...
893	full1 = RLayoutFullVert(self, &full_horiz);
894	full2 = RLayoutFullHoriz(self, &full_vert);
895
896	// And return whichever was bigger.
897	return RAreaArea(&full1) > RAreaArea(&full2) ? full1 : full2;
898}
899
900
901
902/**
903 * Figure the best way to stretch an area horizontally without crossing
904 * monitors.
905 *
906 * This is the backend for the f.horizoom ctwm function.
907 */
908RArea
909RLayoutFullHoriz1(const RLayout *self, const RArea *area)
910{
911	// Cheat by using RLayoutFull1() to find the RArea for the monitor
912	// it's most on.
913	RArea target = RLayoutFull1(self, area);
914	int max_y, min_y2;
915
916	// We're stretching horizontally, so the x and width of target (that
917	// monitor) are already right.  But we have to figure the y and
918	// height...
919
920	// Generally, the y is the window's original y, unless we had to move
921	// it down to get onto the target monitor.  XXX Wait, what if we
922	// moved it _up_?
923	max_y = max(area->y, target.y);
924	target.y = max_y;
925
926	// The bottom would be the bottom of the area, clipped to the bottom
927	// of the monitor.  So the height is the diff.
928	min_y2 = min(RAreaY2(area), RAreaY2(&target));
929	target.height = min_y2 - max_y + 1;
930
931	return target;
932}
933
934
935/**
936 * Figure the best way to stretch an area vertically without crossing
937 * monitors.
938 *
939 * This is the backend for the f.zoom ctwm function.
940 */
941RArea
942RLayoutFullVert1(const RLayout *self, const RArea *area)
943{
944	// Let RLayoutFull1() find the right monitor.
945	RArea target = RLayoutFull1(self, area);
946	int max_x, min_x2;
947
948	// Stretching vertically, so the y/height of the monitor are already
949	// right.
950
951	// x is where the window was, unless we had to move it right to get
952	// onto the monitor.  XXX What if we moved it left?
953	max_x = max(area->x, target.x);
954	target.x = max_x;
955
956	// Right side is where it was, unless we have to clip to the monitor.
957	min_x2 = min(RAreaX2(area), RAreaX2(&target));
958	target.width = min_x2 - max_x + 1;
959
960	return target;
961}
962
963
964/**
965 * Figure the best way to resize an area to fill one monitor.
966 *
967 * This is the backend for the f.fullzoom ctwm function.
968 *
969 * \param self  Monitor layout
970 * \param area  Area (window) to zoom out
971 */
972RArea
973RLayoutFull1(const RLayout *self, const RArea *area)
974{
975	RArea target;
976	RAreaList *mit = RAreaListIntersect(self->monitors, area);
977	// Start with a list of all the monitors the window is on now.
978
979	if(mit->len == 0) {
980		// Not on any screens.  Find the "nearest" place it would wind
981		// up.
982		RAreaListFree(mit);
983		mit = _RLayoutRecenterHorizontally(self, area);
984	}
985
986	// Of the monitors it's on, find the one that it's "most" on, and
987	// return the RArea of it.
988	target = RAreaListBestTarget(mit, area);
989	RAreaListFree(mit);
990	return target;
991}
992
993
994
995/************************
996 *
997 * Finally, some small misc utils.
998 *
999 ************************/
1000
1001
1002/**
1003 * Generate maximal spanning RArea.
1004 *
1005 * This is a trivial wrapper of RAreaListBigArea() to hide knowledge of
1006 * RLayout internals.  Currently only used once; maybe should just be
1007 * deref'd there...
1008 */
1009RArea
1010RLayoutBigArea(const RLayout *self)
1011{
1012	return RAreaListBigArea(self->monitors);
1013}
1014
1015
1016/**
1017 * How many monitors does a given RLayout contain?
1018 */
1019int
1020RLayoutNumMonitors(const RLayout *self)
1021{
1022	return self->monitors->len;
1023}
1024
1025
1026/**
1027 * Pretty-print an RLayout.
1028 *
1029 * Used for dev/debug.
1030 */
1031void
1032RLayoutPrint(const RLayout *self)
1033{
1034	fprintf(stderr, "[monitors=");
1035	RAreaListPrint(self->monitors);
1036	fprintf(stderr, "\n horiz=");
1037	RAreaListPrint(self->horiz);
1038	fprintf(stderr, "\n vert=");
1039	RAreaListPrint(self->vert);
1040	fprintf(stderr, "]\n");
1041}
1042