ウィンドウの移動とリサイズ
今回は、ユーザーのポインタ操作による、ウィンドウの移動とリサイズをやってみます。
ウィンドウの上下左右の端でリサイズ操作、それ以外の範囲内で、ウィンドウ位置の移動を行います。
対応する位置によってカーソル形状が変わるので、左ボタンのドラッグで、移動またはリサイズの操作を行います。
中ボタン押しで終了します。
<13-resize.c>
$ cc -o test 13-resize.c client.c imagebuf.c xdg-shell-protocol.c -lwayland-client -lwayland-cursor -lrt
ウィンドウの上下左右の端でリサイズ操作、それ以外の範囲内で、ウィンドウ位置の移動を行います。
対応する位置によってカーソル形状が変わるので、左ボタンのドラッグで、移動またはリサイズの操作を行います。
中ボタン押しで終了します。
<13-resize.c>
#include <stdio.h> #include <linux/input-event-codes.h> #include <wayland-client.h> #include <wayland-cursor.h> #include "xdg-shell-client-protocol.h" #include "client.h" #include "imagebuf.h" typedef struct { client b; Toplevel *win; uint32_t serial_enter; int last_edge; //カーソルデータ struct wl_surface *cursor_surface; struct wl_cursor_theme *cursor_theme; int cursor_current; //現在のカーソル形状 }client_ex; //------------- //サイズ変更の枠の幅 #define FRAME_WIDTH 5 //カーソルの形状 enum { CURSOR_MOVE, CURSOR_RESIZE_LEFT, CURSOR_RESIZE_RIGHT, CURSOR_RESIZE_TOP, CURSOR_RESIZE_BOTTOM, CURSOR_RESIZE_TOP_LEFT, CURSOR_RESIZE_TOP_RIGHT, CURSOR_RESIZE_BOTTOM_LEFT, CURSOR_RESIZE_BOTTOM_RIGHT }; //形状に対応するカーソル名 static const char *g_cursor_name[] = { "move", "left_side", "right_side", "top_side", "bottom_side", "top_left_corner", "top_right_corner", "bottom_left_corner", "bottom_right_corner" }; static const uint8_t g_edge_to_cursor[] = { CURSOR_MOVE, CURSOR_RESIZE_TOP, CURSOR_RESIZE_BOTTOM, 0, CURSOR_RESIZE_LEFT, CURSOR_RESIZE_TOP_LEFT, CURSOR_RESIZE_BOTTOM_LEFT, 0, CURSOR_RESIZE_RIGHT, CURSOR_RESIZE_TOP_RIGHT, CURSOR_RESIZE_BOTTOM_RIGHT }; //===================== // sub //===================== /* カーソル変更 */ static void _set_cursor(client_ex *p,int no) { struct wl_cursor *cursor; struct wl_cursor_image *img; struct wl_buffer *buffer; //現在と同じ画像ならそのまま if(p->cursor_current == no) return; p->cursor_current = no; printf("* change cursor: '%s'\n", g_cursor_name[no]); //カーソル変更 cursor = wl_cursor_theme_get_cursor(p->cursor_theme, g_cursor_name[no]); if(!cursor) return; img = cursor->images[0]; buffer = wl_cursor_image_get_buffer(img); if(!buffer) return; wl_surface_attach(p->cursor_surface, buffer, 0, 0); wl_surface_damage(p->cursor_surface, 0, 0, img->width, img->height); wl_surface_commit(p->cursor_surface); wl_pointer_set_cursor(p->b.pointer, p->serial_enter, p->cursor_surface, img->hotspot_x, img->hotspot_y); } /* サーフェス内の位置がどのエリア内か */ static int _get_point_edge(Toplevel *win,int x,int y) { int f = 0,w,h; w = win->sf.img->width; h = win->sf.img->height; if(x < FRAME_WIDTH) f |= XDG_TOPLEVEL_RESIZE_EDGE_LEFT; else if(x >= w - FRAME_WIDTH) f |= XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; if(y < FRAME_WIDTH) f |= XDG_TOPLEVEL_RESIZE_EDGE_TOP; else if(y >= h - FRAME_WIDTH) f |= XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; return f; } /* ポインタの enter/motion 時 */ static void _enter_motion(client_ex *p,wl_fixed_t x,wl_fixed_t y) { int edge; x >>= 8; y >>= 8; edge = _get_point_edge(p->win, x, y); p->last_edge = edge; //カーソル変更 _set_cursor(p, g_edge_to_cursor[edge]); } //===================== // toplevel //===================== /* xdg_toplevel:configure */ static void _toplevel_configure(Toplevel *p,int width,int height) { printf("xdg_toplevel # configure | %d x %d\n", width, height); if(width && height) { if(toplevel_resize(p, width, height)) imagebuf_fill(p->sf.img, 0xffff0000); } } //===================== // wl_pointer //===================== static void _pointer_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y) { client_ex *p = (client_ex *)data; printf("wl_pointer # enter\n"); p->serial_enter = serial; _enter_motion(p, x, y); } static void _pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { printf("wl_pointer # leave\n"); //次の enter 時、常にカーソルをセットさせる ((client_ex *)data)->cursor_current = -1; } static void _pointer_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { _enter_motion((client_ex *)data, x, y); } static void _pointer_button(void *data, struct wl_pointer *pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { client_ex *p = (client_ex *)data; if(state != WL_POINTER_BUTTON_STATE_PRESSED) return; if(button == BTN_MIDDLE) //中ボタンで終了 p->b.finish_loop = 1; else if(button == BTN_LEFT) { //左ボタン if(p->last_edge == XDG_TOPLEVEL_RESIZE_EDGE_NONE) //ユーザーによるウィンドウ位置の移動を要求 xdg_toplevel_move(p->win->xdg_toplevel, p->b.seat, serial); else { //ユーザーによるリサイズを要求 xdg_toplevel_resize(p->win->xdg_toplevel, p->b.seat, serial, p->last_edge); } } } static void _pointer_axis(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { } static void _pointer_frame(void *data, struct wl_pointer *pointer) { } static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { } static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis) { } static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete) { } //ver 5 static const struct wl_pointer_listener g_pointer_listener = { _pointer_enter, _pointer_leave, _pointer_motion, _pointer_button, _pointer_axis, _pointer_frame, _pointer_axis_source, _pointer_axis_stop, _pointer_axis_discrete }; //===================== static void _clientex_destroy(client *cl) { client_ex *p = (client_ex *)cl; wl_cursor_theme_destroy(p->cursor_theme); wl_surface_destroy(p->cursor_surface); } /* main */ int main(void) { client_ex *p; Toplevel *win; p = (client_ex *)client_new(sizeof(client_ex)); p->b.destroy = _clientex_destroy; p->b.init_flags = CLIENT_INIT_FLAGS_SEAT | CLIENT_INIT_FLAGS_POINTER; p->b.pointer_listener = &g_pointer_listener; client_init(CLIENT(p)); //カーソル p->cursor_current = -1; p->cursor_surface = wl_compositor_create_surface(p->b.compositor); p->cursor_theme = wl_cursor_theme_load(NULL, 32, p->b.shm); //ウィンドウ p->win = win = toplevel_create(CLIENT(p), 256, 256, NULL); win->func_toplevel_configure = _toplevel_configure; imagebuf_fill(win->sf.img, 0xffff0000); xdg_toplevel_set_min_size(win->xdg_toplevel, 50, 50); // client_loop_simple(CLIENT(p)); //解放 toplevel_destroy(win); client_destroy(CLIENT(p)); return 0; }
解説
通常のウィンドウには、タイトルバーやウィンドウ枠といった、ウィンドウ装飾が付属しています。
タイトルバーをドラッグすると、ウィンドウ位置が移動できたり、ウィンドウ枠をドラッグすると、ウィンドウサイズが変更できたりしますが、Wayland では、それらの処理はすべてクライアントが実装する必要があります。
今回の場合は、ウィンドウの端の部分が左ドラッグされたら、リサイズ操作を行い、それ以外の内側が左ドラッグされたら、ウィンドウ位置の移動を行います。
タイトルバーをドラッグすると、ウィンドウ位置が移動できたり、ウィンドウ枠をドラッグすると、ウィンドウサイズが変更できたりしますが、Wayland では、それらの処理はすべてクライアントが実装する必要があります。
今回の場合は、ウィンドウの端の部分が左ドラッグされたら、リサイズ操作を行い、それ以外の内側が左ドラッグされたら、ウィンドウ位置の移動を行います。
wl_pointer : enter/motion イベント
enter と motion 時に、現在のポインタ位置から、その位置がリサイズのエリアなのか、移動のエリアなのかを判定します。
リサイズの場合は、上下左右4方向に加えて、斜めの4方向にも対応しています。
xdg_toplevel でリサイズの操作を行わせる場合は、以下の値で、どの端をリサイズするかを指定できます。
TOP, BOTTOM, LEFT, RIGHT がぞれぞれフラグになっており、TOP_LEFT は (TOP | LEFT) という形で指定できるので、この値を、そのままエリアの値として使います。
NONE の場合は、移動のエリアになります。
ポインタの移動時にエリアが変わった場合は、それに対応したカーソル形状に変更します。
ただし、今回は、カーソルのアニメーションには対応していません。
リサイズの場合は、上下左右4方向に加えて、斜めの4方向にも対応しています。
xdg_toplevel でリサイズの操作を行わせる場合は、以下の値で、どの端をリサイズするかを指定できます。
enum xdg_toplevel_resize_edge { XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, };
TOP, BOTTOM, LEFT, RIGHT がぞれぞれフラグになっており、TOP_LEFT は (TOP | LEFT) という形で指定できるので、この値を、そのままエリアの値として使います。
NONE の場合は、移動のエリアになります。
ポインタの移動時にエリアが変わった場合は、それに対応したカーソル形状に変更します。
ただし、今回は、カーソルのアニメーションには対応していません。
wl_pointer : button イベント
左ボタンが押された時、現在のポインタのエリア位置によって、ウィンドウの端ならリサイズ、それ以外はウィンドウ位置の移動を行います。
これらの関数を実行した場合、サーバー側が、渡された wl_seat を使って、ユーザーによるウィンドウ位置の移動・リサイズの動作を開始します。
edges は、サイズを変更する端の位置で、enum xdg_toplevel_resize_edge の値です。
ボタンが押されている間、カーソル形状は、サーバー側で自動的に変更されます。
なお、これらの処理が開始された時は、元のサーフェスに leave イベントが送信され、ボタンが離されて操作が終わった時に、ポインタがクライアントのウィンドウ上にあれば、ポインタの下にあるサーフェスで enter イベントが送信されます。
//移動の操作を開始 void xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial); //リサイズの操作を開始 void xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges);
これらの関数を実行した場合、サーバー側が、渡された wl_seat を使って、ユーザーによるウィンドウ位置の移動・リサイズの動作を開始します。
edges は、サイズを変更する端の位置で、enum xdg_toplevel_resize_edge の値です。
ボタンが押されている間、カーソル形状は、サーバー側で自動的に変更されます。
なお、これらの処理が開始された時は、元のサーフェスに leave イベントが送信され、ボタンが離されて操作が終わった時に、ポインタがクライアントのウィンドウ上にあれば、ポインタの下にあるサーフェスで enter イベントが送信されます。
リサイズ処理
リサイズ動作中、またはリサイズが確定した時に、ウィンドウのサイズを変更する必要がある場合は、xdg_toplevel の configure イベントが送信されるので、width, height 引数を元に、ウィンドウのイメージをリサイズして、再描画を行う必要があります。
(width, height が 0 の場合、状態の変更だけで、サイズの変更はありません)
リサイズ動作中は、states 引数に XDG_TOPLEVEL_STATE_RESIZING の値が入るので、リサイズ中に独自の処理を行いたい場合は、この値で判断します。
クライアント側は、ウィンドウサイズを、渡された width, height より大きいサイズには変更できませんが、これより小さいサイズにすることはできます。
今回は xdg_toplevel_set_min_size() を使って、ウィンドウの最小サイズを 50 x 50 にしているので、リサイズ中は 50 x 50 より小さいサイズが来ることはありません。
(ただし、サーバーの実装にもよる)
(width, height が 0 の場合、状態の変更だけで、サイズの変更はありません)
リサイズ動作中は、states 引数に XDG_TOPLEVEL_STATE_RESIZING の値が入るので、リサイズ中に独自の処理を行いたい場合は、この値で判断します。
クライアント側は、ウィンドウサイズを、渡された width, height より大きいサイズには変更できませんが、これより小さいサイズにすることはできます。
今回は xdg_toplevel_set_min_size() を使って、ウィンドウの最小サイズを 50 x 50 にしているので、リサイズ中は 50 x 50 より小さいサイズが来ることはありません。
(ただし、サーバーの実装にもよる)