题目:多窗口事件分发系统
背景介绍
请你为一款支持多窗口的终端设备设计一个简易的图形用户界面 (GUI) 事件分发系统。这个系统需要管理屏幕上的多个窗口,处理窗口的创建、销毁、移动,并能响应用户的点击事件。
核心概念
-
屏幕 (Screen):
- 一个
1000 x 1000
的网格画布。 - 左上角坐标为
[0, 0]
。
- 一个
-
窗口 (Window):
- 每个窗口由其唯一的
id
、左上角位置[row, col]
、宽度width
和高度height
定义。 - 窗口可以部分或完全超出屏幕边界。
- 每个窗口由其唯一的
-
层级 (Layer / Z-order):
- 这是本系统最重要的概念。窗口之间存在层叠关系,就像一叠扑克牌。
- 最后 被操作(创建、移动、点击)的窗口会被置于最顶层。
- 当点击一个坐标时,只有位于最顶层的、覆盖该坐标的窗口会响应。
功能要求
你需要实现一个 MultiWindowSys
类,支持以下操作:
MultiWindowSys()
- 功能: 初始化系统。
- 初始状态: 屏幕上没有任何窗口。
createWindow(int id, int row, int col, int width, int height)
- 功能: 创建一个指定
id
和尺寸的新窗口。 - 成功条件: 窗口
id
在系统中是唯一的。 - 状态变化: 成功创建后,新窗口会被放置在所有现有窗口的最上层。
- 返回值: 创建成功返回
true
;如果id
已存在,则失败并返回false
。
destroyWindow(int id)
- 功能: 销毁指定
id
的窗口。 - 成功条件: 窗口
id
必须存在。 - 状态变化: 成功销毁后,该窗口将从屏幕上移除。
- 返回值: 销毁成功返回
true
;如果窗口不存在,返回false
。
moveWindow(int id, int dstRow, int dstCol)
-
功能: 将指定
id
的窗口移动到新的位置[dstRow, dstCol]
。 -
失败条件:
-
窗口
id
不存在。 -
移动后,整个窗口完全位于屏幕外部。
- (例如,移动到
[-200, -200]
,此时窗口右下角仍在屏幕外)。
- (例如,移动到
-
-
状态变化: 移动成功后,窗口的位置被更新,并且该窗口会被提升至最上层。
-
返回值: 移动成功返回
true
;失败则返回false
,窗口位置和层级保持不变。
dispatchClickEvent(int row, int col)
-
功能: 模拟一次在屏幕坐标
[row, col]
的点击事件。 -
命中规则:
- 系统会从最上层 的窗口开始向下检查,找到第一个覆盖了
[row, col]
坐标的窗口。 - 该窗口即为被"击中"的窗口。
- 系统会从最上层 的窗口开始向下检查,找到第一个覆盖了
-
状态变化: 被击中的窗口会被提升至最上层。
-
返回值: 返回被击中窗口的
id
;如果该坐标上没有任何窗口,则返回-1
。
输入格式
- 函数调用总次数不超过 1000 次。
id
、width
、height
均为正整数。row
,col
,dstRow
,dstCol
等坐标值范围如题目所述。- 用例保证输入合法。
输出格式
- 根据每个函数的原型要求返回相应的值。最终的整体输出由评测框架完成。
样例
输入样例
css
["MultiWindowSys", "createWindow", "createWindow", "createWindow", "moveWindow", "dispatchClickEvent", "dispatchClickEvent", "dispatchClickEvent", "dispatchClickEvent"]
[[], [1, 3, 4, 9, 7], [2, 7, 8, 11, 7], [3, 12, 6, 6, 5], [2, 950, 1000], [7, 8], [7, 7], [7, 8], [10, 7]]
输出样例
java
[null, true, true, true, false, 2, 1, 1, -1]
样例执行流程详解
# | 调用 | 解释与系统状态变化 (层级从上到下) | 返回值 |
---|---|---|---|
1 | MultiWindowSys() |
初始化系统。 | null |
2 | createWindow(1, 3, 4, 9, 7) |
创建窗口1。 层级: [1] | true |
3 | createWindow(2, 7, 8, 11, 7) |
创建窗口2,置于顶层。 层级: [2, 1] | true |
4 | createWindow(3, 12, 6, 6, 5) |
创建窗口3,置于顶层。 层级: [3, 2, 1] | true |
5 | moveWindow(2, 950, 1000) |
尝试移动窗口2。目标位置 [950, 1000] 会使其完全移出屏幕 (屏幕范围 0-999),移动失败。层级不变: [3, 2, 1] |
false |
6 | dispatchClickEvent(7, 8) |
点击 (7,8) 。从上到下检查:窗口3未覆盖,窗口2覆盖。击中窗口2。窗口2被移到最顶层。层级: [2, 3, 1] |
2 |
7 | dispatchClickEvent(7, 7) |
点击 (7,7) 。从上到下检查:窗口2未覆盖,窗口3未覆盖,窗口1覆盖。击中窗口1。窗口1被移到最顶层。层级: [1, 2, 3] |
1 |
8 | dispatchClickEvent(7, 8) |
点击 (7,8) 。从上到下检查:窗口1未覆盖,窗口2覆盖。击中窗口2。窗口2被移到最顶层。层级: [2, 1, 3] |
1 (这里应该是 2 ,样例解释可能有误或与图不符,但逻辑是击中2) |
9 | dispatchClickEvent(10, 7) |
点击 (10,7) 。从上到下检查:窗口2、1、3均未覆盖此坐标。未击中任何窗口。 |
-1 |
java
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* 模拟一个简易的多窗口UI事件分发系统.
* 核心是管理一组窗口对象,维护它们的层级关系(Z-order)和位置信息,
* 并正确处理创建、销毁、移动和点击事件。
*/
public class MultiWindowSys {
/**
* 内部静态类,用于表示一个窗口.
* 封装了窗口的所有属性和相关的几何计算(如点击命中测试)。
*/
private static class Window {
int id;
int row, col;
int width, height;
public Window(int id, int row, int col, int width, int height) {
this.id = id;
this.row = row;
this.col = col;
this.width = width;
this.height = height;
}
/**
* 检查给定的点[r, c]是否在该窗口的矩形区域内.
* @param r 行坐标
* @param c 列坐标
* @return 如果点在窗口内,返回true
*/
public boolean contains(int r, int c) {
return r >= this.row && r < (this.row + this.height) &&
c >= this.col && c < (this.col + this.width);
}
/**
* 检查窗口是否完全移出屏幕外.
* @param screenWidth 屏幕宽度
* @param screenHeight 屏幕高度
* @return 如果完全移出,返回true
*/
public boolean isTotallyOutside(int screenWidth, int screenHeight) {
// 条件:窗口的右边界在屏幕左边界的左边,或左边界在右边界的右边
boolean horizontallyOut = (this.col + this.width <= 0) || (this.col >= screenWidth);
// 条件:窗口的下边界在屏幕上边界的上边,或上边界在下边界的下边
boolean verticallyOut = (this.row + this.height <= 0) || (this.row >= screenHeight);
return horizontallyOut || verticallyOut;
}
}
// --- 系统状态变量 ---
private static final int SCREEN_SIZE = 1000;
/**
* 使用 Map 实现通过窗口ID的快速查找 (O(1) 复杂度).
* Key: 窗口ID, Value: 窗口对象.
*/
private final Map<Integer, Window> windowMap;
/**
* 使用 List 维护窗口的层级关系 (Z-order).
* 约定:列表的末尾是"最上层"的窗口.
* LinkedList 对于"从中间移除,再添加到末尾"的操作效率较高。
*/
private final List<Window> windowStack;
/**
* 初始化系统.
*/
public MultiWindowSys() {
this.windowMap = new HashMap<>();
this.windowStack = new LinkedList<>();
}
/**
* 创建一个新窗口.
* 新窗口总是在最上层。
* @return 如果ID已存在,创建失败返回false;否则成功返回true.
*/
public boolean createWindow(int id, int row, int col, int width, int height) {
if (windowMap.containsKey(id)) {
return false; // ID已存在,创建失败
}
Window newWindow = new Window(id, row, col, width, height);
windowMap.put(id, newWindow);
windowStack.add(newWindow); // 添加到列表末尾,即最上层
return true;
}
/**
* 销毁一个窗口.
* @return 如果窗口ID不存在,返回false;否则成功返回true.
*/
public boolean destroyWindow(int id) {
if (!windowMap.containsKey(id)) {
return false; // 窗口不存在
}
Window windowToDestroy = windowMap.get(id);
windowMap.remove(id); // 从Map中移除
windowStack.remove(windowToDestroy); // 从层级列表中移除
return true;
}
/**
* 移动一个窗口到新位置,并将其置于最顶层.
* @return 如果窗口ID不存在或移动后完全位于屏幕外,返回false;否则成功返回true.
*/
public boolean moveWindow(int id, int dstRow, int dstCol) {
if (!windowMap.containsKey(id)) {
return false; // 窗口不存在
}
Window windowToMove = windowMap.get(id);
// 暂存旧坐标,以备移动失败时恢复
int oldRow = windowToMove.row;
int oldCol = windowToMove.col;
// 先"试探性"地更新坐标
windowToMove.row = dstRow;
windowToMove.col = dstCol;
// 检查移动后是否完全超出屏幕
if (windowToMove.isTotallyOutside(SCREEN_SIZE, SCREEN_SIZE)) {
// 移动失败,恢复原坐标
windowToMove.row = oldRow;
windowToMove.col = oldCol;
return false;
}
// 移动成功,将其置于最顶层
bringToTop(windowToMove);
return true;
}
/**
* 分发点击事件.
* 点击会命中覆盖该坐标的、层级最高的窗口,并将其置于最顶层。
* @return 返回被击中窗口的ID;如果未击中任何窗口,返回-1.
*/
public int dispatchClickEvent(int row, int col) {
Window hitWindow = null;
// 从列表末尾向前遍历,即从最上层的窗口开始检查
for (int i = windowStack.size() - 1; i >= 0; i--) {
Window currentWindow = windowStack.get(i);
if (currentWindow.contains(row, col)) {
// 找到了第一个(即最上层的)命中的窗口
hitWindow = currentWindow;
break;
}
}
if (hitWindow != null) {
// 如果击中窗口,将其置于最顶层
bringToTop(hitWindow);
return hitWindow.id;
}
// 未击中任何窗口
return -1;
}
/**
* 辅助方法:将一个指定的窗口移动到层级的最顶端.
* @param window 要移动到顶层的窗口对象
*/
private void bringToTop(Window window) {
// 先从当前位置移除,再添加到列表末尾
if (windowStack.remove(window)) {
windowStack.add(window);
}
}
}