summaryrefslogtreecommitdiff
path: root/src/screen
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2023-11-06 11:54:08 -0500
committerAndrew Opalach <andrew@akon.city> 2023-11-06 11:54:08 -0500
commita48a68cfb04b2020737c0adfb0e7667451a22b5c (patch)
treea92121897f84298c3798a4f2ba56de45cd100eab /src/screen
downloadcamu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.gz
camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.tar.bz2
camu-a48a68cfb04b2020737c0adfb0e7667451a22b5c.zip
Add camu
- Subprojects temporarily omitted Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/screen')
-rw-r--r--src/screen/meson.build4
-rw-r--r--src/screen/screen.c402
-rw-r--r--src/screen/screen.h78
-rw-r--r--src/screen/view.c28
-rw-r--r--src/screen/view.h23
5 files changed, 535 insertions, 0 deletions
diff --git a/src/screen/meson.build b/src/screen/meson.build
new file mode 100644
index 0000000..cd78988
--- /dev/null
+++ b/src/screen/meson.build
@@ -0,0 +1,4 @@
+screen_src = ['screen.c', 'view.c']
+sekihi = subproject('sekihi').get_variable('sekihi')
+screen = declare_dependency(sources: screen_src,
+ dependencies: [sekihi])
diff --git a/src/screen/screen.c b/src/screen/screen.c
new file mode 100644
index 0000000..82c7b12
--- /dev/null
+++ b/src/screen/screen.c
@@ -0,0 +1,402 @@
+#include <aki/thread.h>
+#include <al/log.h>
+
+#include "screen.h"
+
+#define ZOOM_MAX 100.0
+#define ZOOM_MIN 0.02
+
+#define PAN_BOUNDS 35.0
+
+#define X_IN_BOUNDS(scr, view, x) \
+ ((x + PAN_BOUNDS <= scr->width) && (PAN_BOUNDS - x <= view->width * view->zoom))
+#define Y_IN_BOUNDS(scr, view, y) \
+ ((y + PAN_BOUNDS <= scr->height) && (PAN_BOUNDS - y <= view->height * view->zoom))
+
+#define CAMU_DEFAULT_VIEW CAMU_VIEW_AUTOFIT
+
+static struct camu_view *get_view_from_mouse_pos(struct camu_screen *scr)
+{
+ struct camu_view *view = NULL;
+ struct camu_screen_video *video;
+ al_array_foreach_ptr(scr->videos, i, video) {
+ if (!view) {
+ view = &video->view;
+ continue;
+ }
+ if (view->zindex < video->view.zindex) {
+ view = &video->view;
+ }
+ }
+ return view;
+}
+
+static void should_close_callback(void *userdata)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, 0.0);
+}
+
+static void resize_callback(void *userdata, s32 width, s32 height)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ if (scr->renderer) {
+ scr->renderer->resize(scr->renderer, &width, &height);
+ }
+ scr->width = width;
+ scr->height = height;
+ struct camu_screen_video *video;
+ al_array_foreach_ptr(scr->videos, i, video) {
+ camu_view_calculate(&video->view, scr->width, scr->height);
+ }
+}
+
+static void pointer_pos_callback(void *userdata, f64 x, f64 y)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ struct camu_view *view = get_view_from_mouse_pos(scr);
+ if (view) {
+ if (scr->flags & CAMU_SCREEN_DRAGGING) {
+ if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
+ f64 nx = view->x_offset + x - scr->last_mouse_x;
+ if (X_IN_BOUNDS(scr, view, nx)) {
+ view->x_offset = nx;
+ }
+ f64 ny = view->y_offset + y - scr->last_mouse_y;
+ if (Y_IN_BOUNDS(scr, view, ny)) {
+ view->y_offset = ny;
+ }
+ view->mode = CAMU_VIEW_DETACHED;
+ }
+ scr->last_click_ts = 0;
+ }
+ }
+ scr->last_mouse_x = x;
+ scr->last_mouse_y = y;
+}
+
+static void mouse_button_callback(void *userdata, u8 state, u8 button)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ switch (button) {
+ case SEKIHI_MOUSE1:
+ switch (state) {
+ case SEKIHI_BUTTON_PRESSED:
+ scr->flags |= CAMU_SCREEN_DRAGGING;
+ scr->last_click_ts = aki_get_timestamp();
+ break;
+ case SEKIHI_BUTTON_RELEASED:
+ scr->flags &= ~CAMU_SCREEN_DRAGGING;
+ if (scr->last_click_ts && aki_get_timestamp() - scr->last_click_ts <= 300000) {
+ if (scr->flags & CAMU_SCREEN_MODIFIER) {
+ scr->callback(scr->userdata, CAMU_SCREEN_SEEK, scr->last_mouse_x / scr->width);
+ } else {
+ if (scr->last_mouse_x >= scr->width / 2.f) {
+ scr->callback(scr->userdata, CAMU_SCREEN_NEXT, 0.0);
+ } else {
+ scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, 0.0);
+ }
+ }
+ }
+ break;
+ }
+ break;
+ case SEKIHI_MOUSE2:
+ break; // Move window.
+ default:
+ break;
+ }
+}
+
+static void zoom_on_point(struct camu_screen *scr, struct camu_view *view, f64 x, f64 y, f64 v)
+{
+ x -= view->x_offset;
+ y -= view->y_offset;
+ x /= view->zoom;
+ y /= view->zoom;
+
+ f64 prev_width = scr->width * view->zoom;
+ f64 prev_height = scr->height * view->zoom;
+
+ f64 x_ratio = x / (f64)scr->width;
+ f64 y_ratio = y / (f64)scr->height;
+
+ f64 zoom = view->zoom + (v * view->zoom);
+ if (zoom > ZOOM_MAX || zoom < ZOOM_MIN) return;
+
+ f64 nx = view->x_offset - x_ratio * ((scr->width * zoom) - prev_width);
+ f64 ny = view->y_offset - y_ratio * ((scr->height * zoom) - prev_height);
+ if (X_IN_BOUNDS(scr, view, nx) && Y_IN_BOUNDS(scr, view, ny)) {
+ view->x_offset = nx;
+ view->y_offset = ny;
+ view->zoom = zoom;
+ }
+}
+
+static void scroll_callback(void *userdata, f64 y)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+
+ struct camu_view *view = get_view_from_mouse_pos(scr);
+ if (!view) return;
+
+ if (scr->flags & CAMU_SCREEN_ZOOM_PAN_SIMPLE) {
+ y = -y;
+#if defined SEKIHI_WINDOW_GLFW
+ y /= 4.5;
+#elif defined SEKIHI_WINDOW_WAYLAND
+ y /= 100.0;
+#else
+ y /= 14.0;
+#endif
+ zoom_on_point(scr, view, scr->last_mouse_x, scr->last_mouse_y, y);
+ view->mode = CAMU_VIEW_DETACHED;
+ }
+}
+
+static void key_callback(void *userdata, u8 state, u8 button)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ switch (state) {
+ case SEKIHI_BUTTON_PRESSED:
+ switch (button) {
+ case 0x10:
+ case 0x18:
+ case 'q':
+ scr->callback(scr->userdata, CAMU_SCREEN_CLOSE, 0.0);
+ break;
+ case 0x6a:
+ case 0x31:
+ case 'n':
+ scr->callback(scr->userdata, CAMU_SCREEN_NEXT, 0.0);
+ break;
+ case 0x69:
+ case 0x30:
+ case 'b':
+ scr->callback(scr->userdata, CAMU_SCREEN_PREVIOUS, 0.0);
+ break;
+ case 0x41:
+ case 0x39:
+ scr->callback(scr->userdata, CAMU_SCREEN_TOGGLE_PAUSE, 0.0);
+ break;
+ case 0x13:
+ case 0x1b:
+ case 'r': {
+ struct camu_view *view = get_view_from_mouse_pos(scr);
+ if (!view) return;
+ view->mode = CAMU_DEFAULT_VIEW;
+ camu_view_calculate(view, scr->width, scr->height);
+ break;
+ }
+ case 0x1d:
+ scr->flags |= CAMU_SCREEN_MODIFIER;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SEKIHI_BUTTON_RELEASED:
+ switch (button) {
+ case 0x1d:
+ scr->flags &= ~CAMU_SCREEN_MODIFIER;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void key_immediate_callback(void *userdata, u8 state, u8 button)
+{
+ struct camu_screen *scr = (struct camu_screen *)userdata;
+ switch (state) {
+ case SEKIHI_BUTTON_PRESSED:
+ switch (button) {
+ case 0x29:
+ case 0x21:
+ scr->window->toggle_fullscreen(scr->window);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+bool camu_screen_init(struct camu_screen *scr)
+{
+ al_atomic_s32_store(&scr->state, CAMU_SCREEN_PAUSED, AL_ATOMIC_RELAXED);
+ al_array_init(scr->videos);
+ scr->renderer = NULL;
+ scr->window = skh_window_create();
+ scr->window->pointer_pos_callback = pointer_pos_callback;
+ scr->window->scroll_callback = scroll_callback;
+ scr->window->mouse_button_callback = mouse_button_callback;
+ scr->window->key_callback = key_callback;
+ scr->window->key_immediate_callback = key_immediate_callback;
+ scr->window->resize_callback = resize_callback;
+ scr->window->should_close_callback = should_close_callback;
+ scr->window->userdata = scr;
+#ifdef CAMU_SCREEN_THREADED
+ al_array_init(scr->add_queue);
+ al_array_init(scr->rem_queue);
+ al_atomic_bool_store(&scr->queued, false, AL_ATOMIC_RELAXED);
+ aki_mutex_init(&scr->mutex);
+#endif
+ scr->last_mouse_x = 0;
+ scr->last_mouse_y = 0;
+ scr->flags = CAMU_SCREEN_ZOOM_PAN_SIMPLE;
+ return true;
+}
+
+bool camu_screen_create_window(struct camu_screen *scr, const char *name)
+{
+ if (!scr->window->create_window(scr->window, CAMU_SCREEN_WIDTH, CAMU_SCREEN_HEIGHT, name)) {
+ return false;
+ }
+ scr->width = scr->window->width;
+ scr->height = scr->window->height;
+ return true;
+}
+
+bool camu_screen_create_renderer(struct camu_screen *scr, struct camu_renderer *renderer)
+{
+ scr->renderer = renderer;
+ return renderer->create_renderer(renderer, &scr->width, &scr->height,
+ scr->window->poll,
+#if defined CAMU_RENDERER_VULKAN
+ scr->window->get_vk_proc_address,
+ scr->window->vk_create_surface,
+ scr->window->vk_get_extensions,
+#elif defined CAMU_RENDERER_OPENGL
+ scr->window->get_gl_proc_address,
+ scr->window->gl_load_loader,
+ scr->window->gl_make_current,
+ scr->window->gl_release_current,
+ scr->window->gl_swap_buffers,
+#endif
+ scr->window);
+}
+
+static void add_buffer_internal(struct camu_screen *scr, struct camu_video_buffer *buf)
+{
+ struct camu_stream *stream = buf->stream;
+ struct camu_screen_video video = {
+ .buf = buf,
+ .view = {
+ .mode = CAMU_DEFAULT_VIEW,
+ .width = stream->video.width,
+ .height = stream->video.height,
+ .zindex = 0
+ }
+ };
+#ifdef CAMU_SCREEN_THREADED
+ al_atomic_bool_store(&buf->ref, true, AL_ATOMIC_RELAXED);
+#endif
+ camu_view_calculate(&video.view, scr->width, scr->height);
+ al_array_push(scr->videos, video);
+ scr->window->resize(scr->window, stream->video.width, stream->video.height);
+}
+
+static void remove_buffer_internal(struct camu_screen *scr, struct camu_video_buffer *buf)
+{
+ struct camu_screen_video *video;
+ al_array_foreach_ptr(scr->videos, i, video) {
+ if (video->buf == buf) {
+#ifdef CAMU_SCREEN_THREADED
+ al_atomic_bool_store(&buf->ref, false, AL_ATOMIC_RELAXED);
+#endif
+ al_array_remove_at_iter(scr->videos, i);
+ break;
+ }
+ }
+}
+
+void camu_screen_add_buffer(struct camu_screen *scr, struct camu_video_buffer *buf)
+{
+#ifdef CAMU_SCREEN_THREADED
+ aki_mutex_lock(&scr->mutex);
+ al_array_push(scr->add_queue, buf);
+ al_array_reserve(scr->videos, scr->videos.size + scr->add_queue.size);
+ al_atomic_bool_store(&scr->queued, true, AL_ATOMIC_RELAXED);
+ aki_mutex_unlock(&scr->mutex);
+#else
+ add_buffer_internal(scr, buf);
+#endif
+ camu_screen_wake(scr);
+}
+
+#ifdef CAMU_SCREEN_THREADED
+static void run_queue_internal(struct camu_screen *scr)
+{
+ struct camu_video_buffer *buf;
+ al_array_foreach(scr->add_queue, i, buf) {
+ add_buffer_internal(scr, buf);
+ al_array_remove_at_iter(scr->add_queue, i);
+ }
+ al_array_foreach(scr->rem_queue, i, buf) {
+ remove_buffer_internal(scr, buf);
+ al_array_remove_at_iter(scr->rem_queue, i);
+ }
+}
+#endif
+
+void camu_screen_remove_buffer(struct camu_screen *scr, struct camu_video_buffer *buf)
+{
+#ifdef CAMU_SCREEN_THREADED
+ aki_mutex_lock(&scr->mutex);
+ al_array_push(scr->rem_queue, buf);
+ al_atomic_bool_store(&scr->queued, true, AL_ATOMIC_RELAXED);
+ if (al_atomic_s32_load(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_CLOSED) {
+ run_queue_internal(scr);
+ }
+ aki_mutex_unlock(&scr->mutex);
+#else
+ remove_buffer_internal(scr, buf);
+#endif
+ camu_screen_wake(scr);
+}
+
+#ifdef CAMU_SCREEN_THREADED
+void camu_screen_run_queue(struct camu_screen *scr)
+{
+ aki_mutex_lock(&scr->mutex);
+ run_queue_internal(scr);
+ al_atomic_bool_store(&scr->queued, false, AL_ATOMIC_RELAXED);
+ aki_mutex_unlock(&scr->mutex);
+}
+#endif
+
+void camu_screen_set_state(struct camu_screen *scr, u8 state)
+{
+ al_atomic_s32_store(&scr->state, state, AL_ATOMIC_RELAXED);
+}
+
+bool camu_screen_tick(struct camu_screen *scr)
+{
+#if defined SEKIHI_POLL_CONSERVATIVE
+ scr->window->poll(scr->window,
+ al_atomic_s32_load(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_PAUSED);
+ return skh_window_read_events(scr->window);
+#elif defined SEKIHI_POLL_INLINE
+ return scr->window->poll(scr->window,
+ al_atomic_s32_load(&scr->state, AL_ATOMIC_RELAXED) == CAMU_SCREEN_PAUSED);
+#endif
+}
+
+void camu_screen_wake(struct camu_screen *scr)
+{
+#ifdef SEKIHI_PAUSE
+ scr->window->wake(scr->window);
+#endif
+}
+
+void camu_screen_close(struct camu_screen *scr)
+{
+#ifdef CAMU_SCREEN_THREADED
+ al_array_free(scr->add_queue);
+ al_array_free(scr->rem_queue);
+ aki_mutex_destroy(&scr->mutex);
+#endif
+ al_array_free(scr->videos);
+}
diff --git a/src/screen/screen.h b/src/screen/screen.h
new file mode 100644
index 0000000..d45d5ee
--- /dev/null
+++ b/src/screen/screen.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#define CAMU_SCREEN_THREADED
+
+#include <al/array.h>
+#ifdef CAMU_SCREEN_THREADED
+#include <al/atomic.h>
+#include <aki/thread.h>
+#endif
+#include <skh/window.h>
+
+#include "../buffer/video.h"
+#include "../render/renderer.h"
+#include "../codec/codec.h"
+
+#include "view.h"
+
+#define CAMU_SCREEN_WIDTH 700
+#define CAMU_SCREEN_HEIGHT 700
+
+enum {
+ CAMU_SCREEN_ZOOM_PAN_SIMPLE = 1,
+ CAMU_SCREEN_DRAGGING = 1 << 1,
+ CAMU_SCREEN_MODIFIER = 1 << 2,
+};
+
+enum {
+ CAMU_SCREEN_PAUSED = 0,
+ CAMU_SCREEN_PLAYING,
+ CAMU_SCREEN_CLOSED
+};
+
+enum {
+ CAMU_SCREEN_TOGGLE_PAUSE = 0,
+ CAMU_SCREEN_NEXT,
+ CAMU_SCREEN_PREVIOUS,
+ CAMU_SCREEN_SEEK,
+ CAMU_SCREEN_CLOSE
+};
+
+struct camu_screen_video {
+ struct camu_view view;
+ struct camu_video_buffer *buf;
+};
+
+struct camu_screen {
+ atomic_s32 state;
+ u32 flags;
+ struct skh_window *window;
+ struct camu_renderer *renderer;
+ s32 width;
+ s32 height;
+ u64 last_click_ts;
+ f64 last_mouse_y;
+ f64 last_mouse_x;
+ array(struct camu_screen_video) videos;
+#ifdef CAMU_SCREEN_THREADED
+ array(struct camu_video_buffer *) add_queue;
+ array(struct camu_video_buffer *) rem_queue;
+ atomic_bool queued;
+ struct aki_mutex mutex;
+#endif
+ void (*callback)(void *, u8, f64);
+ void *userdata;
+};
+
+bool camu_screen_init(struct camu_screen *scr);
+bool camu_screen_create_window(struct camu_screen *scr, const char *name);
+bool camu_screen_create_renderer(struct camu_screen *scr, struct camu_renderer *renderer);
+void camu_screen_add_buffer(struct camu_screen *scr, struct camu_video_buffer *buf);
+void camu_screen_remove_buffer(struct camu_screen *scr, struct camu_video_buffer *buf);
+#ifdef CAMU_SCREEN_THREADED
+void camu_screen_run_queue(struct camu_screen *scr);
+#endif
+void camu_screen_set_state(struct camu_screen *scr, u8 state);
+bool camu_screen_tick(struct camu_screen *scr);
+void camu_screen_wake(struct camu_screen *scr);
+void camu_screen_close(struct camu_screen *scr);
diff --git a/src/screen/view.c b/src/screen/view.c
new file mode 100644
index 0000000..cf23ed4
--- /dev/null
+++ b/src/screen/view.c
@@ -0,0 +1,28 @@
+#include "view.h"
+
+void camu_view_calculate(struct camu_view *view, s32 window_width, s32 window_height)
+{
+ f64 ratio = window_width / (f64)window_height;
+ f64 frame_ratio = view->width / (f64)view->height;
+ view->ratio = ratio / frame_ratio;
+ switch (view->mode) {
+ case CAMU_VIEW_AUTOFIT:
+ if (frame_ratio > ratio) {
+ view->zoom = (window_width / (f64)view->width);
+ } else {
+ view->zoom = (window_height / (f64)view->height);
+ }
+ break;
+ case CAMU_VIEW_ZOOM:
+ if (frame_ratio < ratio) {
+ view->zoom = (window_width / (f64)view->width);
+ } else {
+ view->zoom = (window_height / (f64)view->height);
+ }
+ break;
+ default:
+ return;
+ }
+ view->x_offset = ((window_width - (view->width * view->zoom)) / 2.0);
+ view->y_offset = ((window_height - (view->height * view->zoom)) / 2.0);
+}
diff --git a/src/screen/view.h b/src/screen/view.h
new file mode 100644
index 0000000..0546062
--- /dev/null
+++ b/src/screen/view.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <al/types.h>
+
+enum {
+ CAMU_VIEW_HIDDEN = 0,
+ CAMU_VIEW_AUTOFIT,
+ CAMU_VIEW_ZOOM,
+ CAMU_VIEW_DETACHED
+};
+
+struct camu_view {
+ u8 mode;
+ s32 width;
+ s32 height;
+ f64 ratio;
+ f64 zoom;
+ f64 x_offset;
+ f64 y_offset;
+ s32 zindex;
+};
+
+void camu_view_calculate(struct camu_view *view, s32 window_width, s32 window_height);