[Bf-blender-cvs] [a420381] master: UI: Panel drag-collapse

Julian Eisel noreply at git.blender.org
Sat Apr 25 05:14:43 CEST 2015


Commit: a4203814d1f52f475aad6fbe90bdeb2edec19d4a
Author: Julian Eisel
Date:   Sat Apr 25 12:56:24 2015 +1000
Branches: master
https://developer.blender.org/rBa4203814d1f52f475aad6fbe90bdeb2edec19d4a

UI: Panel drag-collapse

D1233, Adds a way to quickly open/close multiple panels by holding LMB and dragging over the desired panels,
Suggested by @maxon

The decision if the panels are opened or closed is made based on the first Panel the user drags over.
If it is closed, all panels he drags over are opened
(including the first one) if it's opened, they get closed (matching existing drag-toggle logic).

===================================================================

M	source/blender/editors/interface/interface_panel.c

===================================================================

diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c
index 5aee8e1..239d6f7 100644
--- a/source/blender/editors/interface/interface_panel.c
+++ b/source/blender/editors/interface/interface_panel.c
@@ -82,6 +82,14 @@
 /* only show pin header button for pinned panels */
 #define USE_PIN_HIDDEN
 
+/* the state of the mouse position relative to the panel */
+typedef enum uiPanelMouseState {
+	PANEL_MOUSE_OUTSIDE,        /* mouse is not in the panel */
+	PANEL_MOUSE_INSIDE_CONTENT, /* mouse is in the actual panel content */
+	PANEL_MOUSE_INSIDE_HEADER,  /* mouse is in the panel header */
+	PANEL_MOUSE_INSIDE_SCALE,   /* mouse is inside panel scale widget */
+} uiPanelMouseState;
+
 typedef enum uiHandlePanelState {
 	PANEL_STATE_DRAG,
 	PANEL_STATE_DRAG_SCALE,
@@ -1128,6 +1136,158 @@ static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
 
 /******************* region level panel interaction *****************/
 
+static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, const Panel *pa, const int mx, const int my)
+{
+	/* open panel */
+	if (pa->flag & PNL_CLOSEDX) {
+		if ((block->rect.xmin <= mx) && (block->rect.xmin + PNL_HEADER >= mx)) {
+			return PANEL_MOUSE_INSIDE_HEADER;
+		}
+	}
+	/* outside left/right side */
+	else if ((block->rect.xmin > mx) || (block->rect.xmax < mx)) {
+		/* pass */
+	}
+	else if ((block->rect.ymax <= my) && (block->rect.ymax + PNL_HEADER >= my)) {
+		return PANEL_MOUSE_INSIDE_HEADER;
+	}
+	/* open panel */
+	else if (!(pa->flag & PNL_CLOSEDY)) {
+		if (pa->control & UI_PNL_SCALE) {
+			if (block->rect.xmax - PNL_HEADER <= mx) {
+				if (block->rect.ymin + PNL_HEADER >= my) {
+					return PANEL_MOUSE_INSIDE_SCALE;
+				}
+			}
+		}
+		if ((block->rect.xmin <= mx) && (block->rect.xmax >= mx)) {
+			if ((block->rect.ymin <= my) && (block->rect.ymax + PNL_HEADER >= my)) {
+				return PANEL_MOUSE_INSIDE_CONTENT;
+			}
+		}
+	}
+	return PANEL_MOUSE_OUTSIDE;
+}
+
+typedef struct uiPanelDragCollapseHandle {
+	bool was_first_open;
+	int xy_init[2];
+} uiPanelDragCollapseHandle;
+
+static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata)
+{
+	uiPanelDragCollapseHandle *dragcol_data = userdata;
+	MEM_freeN(dragcol_data);
+}
+
+static void ui_panel_drag_collapse(bContext *C, uiPanelDragCollapseHandle *dragcol_data, const int xy_dst[2])
+{
+	ScrArea *sa = CTX_wm_area(C);
+	ARegion *ar = CTX_wm_region(C);
+	uiBlock *block;
+	Panel *pa;
+
+	for (block = ar->uiblocks.first; block; block = block->next) {
+		float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)};
+		float xy_b_block[2] = {UNPACK2(xy_dst)};
+		rctf rect = block->rect;
+		int oldflag;
+		const bool is_horizontal = (panel_aligned(sa, ar) == BUT_HORIZONTAL);
+
+		if ((pa = block->panel) == 0 || (pa->type && (pa->type->flag & PNL_NO_HEADER))) {
+			continue;
+		}
+		oldflag = pa->flag;
+
+		/* lock one axis */
+		if (is_horizontal) {
+			xy_b_block[1] = dragcol_data->xy_init[1];
+		}
+		else {
+			xy_b_block[0] = dragcol_data->xy_init[0];
+		}
+
+		/* use cursor coords in block space */
+		ui_window_to_block_fl(ar, block, &xy_a_block[0], &xy_a_block[1]);
+		ui_window_to_block_fl(ar, block, &xy_b_block[0], &xy_b_block[1]);
+
+		/* set up rect to match header size */
+		rect.ymin = rect.ymax;
+		rect.ymax = rect.ymin + PNL_HEADER;
+		if (pa->flag & PNL_CLOSEDX) {
+			rect.xmin = rect.xmin;
+			rect.xmax = rect.xmin + PNL_HEADER;
+		}
+
+		/* touch all panels between last mouse coord and the current one */
+		if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) {
+			/* force panel to close */
+			if (dragcol_data->was_first_open == true) {
+				pa->flag |= (is_horizontal ? PNL_CLOSEDX : PNL_CLOSEDY);
+			}
+			/* force panel to open */
+			else {
+				pa->flag &= ~PNL_CLOSED;
+			}
+
+			/* if pa->flag has changed this means a panel was opened/closed here */
+			if (pa->flag != oldflag) {
+				panel_activate_state(C, pa, PANEL_STATE_ANIMATION);
+			}
+		}
+	}
+}
+
+/**
+ * Panel drag-collapse (modal handler)
+ * Clicking and dragging over panels toggles their collapse state based on the panel that was first
+ * dragged over. If it was open all affected panels incl the initial one are closed and vise versa.
+ */
+static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata)
+{
+	wmWindow *win = CTX_wm_window(C);
+	uiPanelDragCollapseHandle *dragcol_data = userdata;
+	short retval = WM_UI_HANDLER_CONTINUE;
+
+	switch (event->type) {
+		case MOUSEMOVE:
+			ui_panel_drag_collapse(C, dragcol_data, &event->x);
+
+			retval = WM_UI_HANDLER_BREAK;
+			break;
+		case LEFTMOUSE:
+			if (event->val == KM_RELEASE) {
+				/* done! */
+				WM_event_remove_ui_handler(
+				        &win->modalhandlers,
+				        ui_panel_drag_collapse_handler,
+				        ui_panel_drag_collapse_handler_remove,
+				        dragcol_data, true);
+				ui_panel_drag_collapse_handler_remove(C, dragcol_data);
+			}
+			/* don't let any left-mouse event fall through! */
+			retval = WM_UI_HANDLER_BREAK;
+			break;
+	}
+
+	return retval;
+}
+
+static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
+{
+	wmWindow *win = CTX_wm_window(C);
+	wmEvent *event = win->eventstate;
+	uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__);
+
+	dragcol_data->was_first_open = was_open;
+	copy_v2_v2_int(dragcol_data->xy_init, &event->x);
+
+	WM_event_add_ui_handler(
+	        C, &win->modalhandlers,
+	        ui_panel_drag_collapse_handler,
+	        ui_panel_drag_collapse_handler_remove,
+	        dragcol_data, false);
+}
 
 /* this function is supposed to call general window drawing too */
 /* also it supposes a block has panel, and isn't a menu */
@@ -1199,15 +1359,27 @@ static void ui_handle_panel_header(const bContext *C, uiBlock *block, int mx, in
 				/* snap back up so full panel aligns with screen edge */
 				if (block->panel->snap & PNL_SNAP_BOTTOM) 
 					block->panel->ofsy = 0;
+
+				if (event == LEFTMOUSE) {
+					ui_panel_drag_collapse_handler_add(C, false);
+				}
 			}
 			else if (align == BUT_HORIZONTAL) {
 				block->panel->flag |= PNL_CLOSEDX;
+
+				if (event == LEFTMOUSE) {
+					ui_panel_drag_collapse_handler_add(C, true);
+				}
 			}
 			else {
-				/* snap down to bottom screen edge*/
+				/* snap down to bottom screen edge */
 				block->panel->flag |= PNL_CLOSEDY;
 				if (block->panel->snap & PNL_SNAP_BOTTOM) 
 					block->panel->ofsy = -block->panel->sizey;
+
+				if (event == LEFTMOUSE) {
+					ui_panel_drag_collapse_handler_add(C, true);
+				}
 			}
 			
 			for (pa = ar->panels.first; pa; pa = pa->next) {
@@ -1722,8 +1894,8 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar)
 	}
 
 	for (block = ar->uiblocks.last; block; block = block->prev) {
-		bool inside = false, inside_header = false, inside_scale = false;
-		
+		uiPanelMouseState mouse_state;
+
 		mx = event->x;
 		my = event->y;
 		ui_window_to_block(ar, block, &mx, &my);
@@ -1735,32 +1907,11 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar)
 			continue;
 		if (pa->type && pa->type->flag & PNL_NO_HEADER)  /* XXX - accessed freed panels when scripts reload, need to fix. */
 			continue;
-		
-		/* clicked at panel header? */
-		if (pa->flag & PNL_CLOSEDX) {
-			if (block->rect.xmin <= mx && block->rect.xmin + PNL_HEADER >= mx)
-				inside_header = true;
-		}
-		else if (block->rect.xmin > mx || block->rect.xmax < mx) {
-			/* outside left/right side */
-		}
-		else if ((block->rect.ymax <= my) && (block->rect.ymax + PNL_HEADER >= my)) {
-			inside_header = true;
-		}
-		else if (!(pa->flag & PNL_CLOSEDY)) {
-			/* open panel */
-			if (pa->control & UI_PNL_SCALE) {
-				if (block->rect.xmax - PNL_HEADER <= mx)
-					if (block->rect.ymin + PNL_HEADER >= my)
-						inside_scale = true;
-			}
-			if (block->rect.xmin <= mx && block->rect.xmax >= mx)
-				if (block->rect.ymin <= my && block->rect.ymax + PNL_HEADER >= my)
-					inside = true;
-		}
-		
+
+		mouse_state = ui_panel_mouse_state_get(block, pa, mx, my);
+
 		/* XXX hardcoded key warning */
-		if ((inside || inside_header) && event->val == KM_PRESS) {
+		if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER) && event->val == KM_PRESS) {
 			if (event->type == AKEY && ((event->ctrl + event->oskey + event->shift + event->alt) == 0)) {
 				
 				if (pa->flag & PNL_CLOSEDY) {
@@ -1779,13 +1930,13 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar)
 		if (ui_but_is_active(ar))
 			continue;
 		
-		if (inside || inside_header) {
+		if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) {
 
 			if (event->val == KM_PRESS) {
 				
 				/* open close on header */
 				if (ELEM(event->type, RETKEY, PADENTER)) {
-					if (inside_header) {
+					if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) {
 						ui_handle_panel_header(C, block, mx, my, RETKEY, event->ctrl, event->shift);
 						retval = WM_UI_HANDLER_BREAK;
 						break;
@@ -1795,12 +1946,12 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar)
 					/* all inside clicks should return in break - overlapping/float panels */
 					retval = WM_UI_HANDLER_BREAK;
 					
-					if (inside_header) {
-						ui_handle_panel_header(C, block, mx, my, 0, event->ctrl, event->shift);
+					if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) {
+						ui_handle_panel_header(C, block, mx, my, event->type, event->ctrl, event->shift);
 						retval = WM_UI_HANDLER_BREAK;
 						break;
 					}
-					else if (inside_scale && !(pa->flag & PNL_CLOSED)) {
+					else if ((mouse_state == PANEL_MOUSE_INSIDE_SCALE) && !(pa->flag & PNL_CLOSED)) {
 						panel_activate_state(C, pa, PANEL_STATE_DRAG_SCALE);
 						retval = WM_UI_HANDLER_BREAK;
 						break;
@@ -1808,7 +1959,7 @@ int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *ar)
 
 				}
 				else if (event->type == RIGHTMOUSE) {
-					if (inside_header) {
+					if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) {
 						ui_panel_menu(C, ar, block->panel);
 						retval = WM_UI_HANDLER_BREAK;
 						break;




More information about the Bf-blender-cvs mailing list