"""자막 창 배치 계산 (모니터 선택 + 디스코드식 9분할).""" from __future__ import annotations import pytest from livesub.ui.placement import ( ANCHOR_LABELS, ANCHORS, DEFAULT_ANCHOR, ScreenInfo, pick_screen, resolve_rect, screen_for_point, ) MAIN = ScreenInfo(0, "DELL U2720Q", 0, 0, 1920, 1080, is_primary=True) #: 오른쪽에 붙은 두 번째 모니터 (음수 좌표가 아닌 일반 배치) SECOND = ScreenInfo(1, "LG 27GP", 1920, 0, 2560, 1440) #: 왼쪽에 붙어 x 가 음수인 모니터 — 윈도우에서 흔한 구성 LEFT = ScreenInfo(1, "왼쪽", -1600, 0, 1600, 900) SCREENS = [MAIN, SECOND] def test_all_nine_anchors_defined_and_labelled(): assert len(ANCHORS) == 9 assert set(ANCHORS) == set(ANCHOR_LABELS) def test_bottom_center_is_default(): x, y, w, h = resolve_rect(MAIN, DEFAULT_ANCHOR, 1100, 200, margin=60) assert x == (1920 - 1100) // 2 assert y == 1080 - 200 - 60 def test_each_corner_lands_in_its_corner(): w, h, m = 400, 150, 50 tl = resolve_rect(MAIN, "top-left", w, h, m) tr = resolve_rect(MAIN, "top-right", w, h, m) bl = resolve_rect(MAIN, "bottom-left", w, h, m) br = resolve_rect(MAIN, "bottom-right", w, h, m) assert tl[0] == m and tl[1] == m assert tr[0] == 1920 - w - m and tr[1] == m assert bl[0] == m and bl[1] == 1080 - h - m assert br[0] == 1920 - w - m and br[1] == 1080 - h - m def test_middle_center_is_centered(): x, y, w, h = resolve_rect(MAIN, "middle-center", 600, 200, 60) assert x == (1920 - 600) // 2 assert y == (1080 - 200) // 2 def test_second_monitor_offsets_are_applied(): """두 번째 모니터는 가상 데스크톱 좌표가 1920부터 시작한다.""" x, y, w, h = resolve_rect(SECOND, "bottom-center", 1100, 200, 60) assert x == 1920 + (2560 - 1100) // 2 assert y == 1440 - 200 - 60 def test_negative_origin_monitor(): x, y, w, h = resolve_rect(LEFT, "top-left", 400, 150, 50) assert x == -1600 + 50 assert y == 50 def test_oversized_window_is_clamped_into_the_screen(): """창이 모니터보다 크면 화면 밖으로 나가면 안 된다.""" x, y, w, h = resolve_rect(LEFT, "bottom-center", 9999, 9999, margin=60) assert w <= LEFT.width assert h <= LEFT.height assert x >= LEFT.x assert y >= LEFT.y assert x + w <= LEFT.x + LEFT.width assert y + h <= LEFT.y + LEFT.height @pytest.mark.parametrize("anchor", list(ANCHORS)) def test_every_anchor_stays_inside_the_screen(anchor): x, y, w, h = resolve_rect(SECOND, anchor, 1100, 200, 60) assert SECOND.x <= x and x + w <= SECOND.x + SECOND.width assert SECOND.y <= y and y + h <= SECOND.y + SECOND.height def test_unknown_anchor_falls_back_to_default(): assert resolve_rect(MAIN, "nope", 500, 200, 60) == resolve_rect( MAIN, DEFAULT_ANCHOR, 500, 200, 60 ) def test_screen_for_point_finds_the_right_monitor(): assert screen_for_point(SCREENS, 100, 100) is MAIN assert screen_for_point(SCREENS, 2500, 700) is SECOND assert screen_for_point(SCREENS, 99_999, 0) is None def test_pick_screen_falls_back_to_primary_when_monitor_disappears(): """모니터를 뽑았을 때 저장된 인덱스가 사라져도 자막이 실종되면 안 된다.""" assert pick_screen(SCREENS, 1) is SECOND assert pick_screen(SCREENS, 7) is MAIN # 없는 인덱스 → 주 모니터 assert pick_screen([], 0) is None def test_pick_screen_uses_first_when_no_primary_flagged(): screens = [ScreenInfo(3, "A", 0, 0, 800, 600), ScreenInfo(4, "B", 800, 0, 800, 600)] assert pick_screen(screens, 99) is screens[0]