diff --git a/src/view.c b/src/view.c index 16e160c9..c07abbfb 100644 --- a/src/view.c +++ b/src/view.c @@ -183,12 +183,81 @@ static void area_make_pair(enum window_node_split split, int gap, float ratio, s } } +static inline bool window_node_is_leaf(struct window_node *node); + +static CGSize window_node_get_min_size(struct window_node *node) +{ + CGSize min_size = { 0, 0 }; + + if (window_node_is_leaf(node)) { + for (int i = 0; i < node->window_count; ++i) { + struct window *window = window_manager_find_window(&g_window_manager, node->window_list[i]); + if (window && window->has_size_constraints) { + min_size.width = max(min_size.width, window->min_size.width); + min_size.height = max(min_size.height, window->min_size.height); + } + } + } else { + CGSize left_min = window_node_get_min_size(node->left); + CGSize right_min = window_node_get_min_size(node->right); + min_size.width = max(left_min.width, right_min.width); + min_size.height = max(left_min.height, right_min.height); + } + + return min_size; +} + static void area_make_pair_for_node(struct view *view, struct window_node *node) { enum window_node_split split = window_node_get_split(view, node); float ratio = window_node_get_ratio(node); int gap = window_node_get_gap(view); + CGSize left_min = window_node_get_min_size(node->left); + CGSize right_min = window_node_get_min_size(node->right); + + if (split == SPLIT_Y) { + float available = node->area.w - gap; + float left_needed = left_min.width; + float right_needed = right_min.width; + + if (left_needed > 0 && left_needed > available * ratio) { + float new_ratio = left_needed / available; + if (new_ratio <= 0.9f) { + ratio = new_ratio; + debug("adjusted ratio to %.2f for left child min_width %.0f\n", ratio, left_needed); + } + } + + if (right_needed > 0 && right_needed > available * (1 - ratio)) { + float new_ratio = 1.0f - (right_needed / available); + if (new_ratio >= 0.1f) { + ratio = new_ratio; + debug("adjusted ratio to %.2f for right child min_width %.0f\n", ratio, right_needed); + } + } + } else { + float available = node->area.h - gap; + float left_needed = left_min.height; + float right_needed = right_min.height; + + if (left_needed > 0 && left_needed > available * ratio) { + float new_ratio = left_needed / available; + if (new_ratio <= 0.9f) { + ratio = new_ratio; + debug("adjusted ratio to %.2f for left child min_height %.0f\n", ratio, left_needed); + } + } + + if (right_needed > 0 && right_needed > available * (1 - ratio)) { + float new_ratio = 1.0f - (right_needed / available); + if (new_ratio >= 0.1f) { + ratio = new_ratio; + debug("adjusted ratio to %.2f for right child min_height %.0f\n", ratio, right_needed); + } + } + } + area_make_pair(split, gap, ratio, &node->area, &node->left->area, &node->right->area); node->split = split; diff --git a/src/window.h b/src/window.h index e5e3893e..d4f2d7b5 100644 --- a/src/window.h +++ b/src/window.h @@ -103,6 +103,9 @@ struct window float opacity; int layer; char *scratchpad; + CGSize min_size; + CGSize max_size; + bool has_size_constraints; }; enum window_flag diff --git a/src/window_manager.c b/src/window_manager.c index b0485528..5dff012d 100644 --- a/src/window_manager.c +++ b/src/window_manager.c @@ -352,6 +352,22 @@ void window_manager_resize_window_relative_internal(struct window *window, CGRec float fw = max(1, frame.size.width + dx * x_mod); float fh = max(1, frame.size.height + dy * y_mod); + + if (window->has_size_constraints) { + if (window->min_size.width > 0 && fw < window->min_size.width) { + fw = window->min_size.width; + } + if (window->min_size.height > 0 && fh < window->min_size.height) { + fh = window->min_size.height; + } + if (window->max_size.width > 0 && fw > window->max_size.width) { + fw = window->max_size.width; + } + if (window->max_size.height > 0 && fh > window->max_size.height) { + fh = window->max_size.height; + } + } + float fx = (direction & HANDLE_LEFT) ? frame.origin.x + frame.size.width - fw : frame.origin.x; float fy = (direction & HANDLE_TOP) ? frame.origin.y + frame.size.height - fh : frame.origin.y; @@ -726,6 +742,52 @@ void window_manager_animate_window(struct window_capture capture) } } +static void window_manager_apply_constrained_position(struct window *window, float target_x, float target_y, + float target_w, float target_h, CGRect actual_frame) +{ + float new_x = target_x; + float new_y = target_y; + + if (actual_frame.size.width < target_w) { + new_x = target_x + (target_w - actual_frame.size.width) / 2.0f; + } + if (actual_frame.size.height < target_h) { + new_y = target_y + (target_h - actual_frame.size.height) / 2.0f; + } + + if (actual_frame.size.width > target_w || actual_frame.size.height > target_h) { + uint32_t did = window_display_id(window->id); + CGRect display_bounds = display_bounds_constrained(did, false); + + float node_center_x = target_x + target_w / 2.0f; + float node_center_y = target_y + target_h / 2.0f; + float display_center_x = display_bounds.origin.x + display_bounds.size.width / 2.0f; + float display_center_y = display_bounds.origin.y + display_bounds.size.height / 2.0f; + + if (actual_frame.size.width > target_w) { + float overflow = actual_frame.size.width - target_w; + if (node_center_x < display_center_x) { + new_x = target_x; + } else { + new_x = target_x - overflow; + } + } + + if (actual_frame.size.height > target_h) { + float overflow = actual_frame.size.height - target_h; + if (node_center_y < display_center_y) { + new_y = target_y; + } else { + new_y = target_y - overflow; + } + } + } + + if (new_x != target_x || new_y != target_y) { + window_manager_move_window(window, new_x, new_y); + } +} + void window_manager_set_window_frame(struct window *window, float x, float y, float width, float height) { // @@ -747,6 +809,45 @@ void window_manager_set_window_frame(struct window *window, float x, float y, fl // NOTE(asmvik): Due to macOS constraints (visible screen-area), we might need to resize the window *after* moving it. window_manager_resize_window(window, width, height); }); + + CGRect actual_frame = window_ax_frame(window); + + if (AX_DIFF(actual_frame.size.width, width) || AX_DIFF(actual_frame.size.height, height)) { + bool first_detection = !window->has_size_constraints; + window->has_size_constraints = true; + + if (actual_frame.size.width > width) { + window->min_size.width = fmax(window->min_size.width, actual_frame.size.width); + } + if (actual_frame.size.height > height) { + window->min_size.height = fmax(window->min_size.height, actual_frame.size.height); + } + if (actual_frame.size.width < width) { + window->max_size.width = (window->max_size.width == 0) + ? actual_frame.size.width + : fmin(window->max_size.width, actual_frame.size.width); + } + if (actual_frame.size.height < height) { + window->max_size.height = (window->max_size.height == 0) + ? actual_frame.size.height + : fmin(window->max_size.height, actual_frame.size.height); + } + + debug("window %d has constraints: min=%.0fx%.0f max=%.0fx%.0f\n", + window->id, + window->min_size.width, window->min_size.height, + window->max_size.width, window->max_size.height); + + window_manager_apply_constrained_position(window, x, y, width, height, actual_frame); + + if (first_detection) { + struct view *view = space_manager_find_view(&g_space_manager, space_manager_active_space()); + if (view) { + view_update(view); + view_flush(view); + } + } + } } void window_manager_set_purify_mode(struct window_manager *wm, enum purify_mode mode)