예제 만들기
처음에는 글래스 모피즘이 들어간 슬라이더를 만들려고 했는데 AI와 함께해도 생각처럼 구현이 안돼서 슬라이더를 만드는 것으로 변경했다. 브랜치
제작 과정
먼저 캔버스나 thumbs 처럼 구현에 필요한 상수들을 다음과 같이 정의했다.
//Layout
#define CANVAS_W 800
#define CANVAS_H 400
#define TRACK_W 640.0f
#define TRACK_X ((CANVAS_W - TRACK_W) * 0.5f)
#define TRACK_CY (CANVAS_H * 0.5f)
#define TRACK_H 20.0f
#define THUMB_W 130.0f
#define THUMB_H 84.0f
기본적으로 다루어야 하는 변수들은 선언, thumb는 그림자 효과를 줄려고 Scene으로 지정했다.
tvg::Shape* blue = nullptr; //filled part of the track
tvg::Scene* thumb = nullptr; //white pill + soft shadow
float value = 0.4f; //0.0 ~ 1.0
bool dragging = false;
채워지는 너비 계산은 다음 함수를 통해서.
//The shared track shape: the groove and the blue fill differ only in width.
void track(tvg::Shape* shape, float width)
{
shape->appendRect(TRACK_X, TRACK_CY - TRACK_H * 0.5f, width, TRACK_H, TRACK_H * 0.5f, TRACK_H * 0.5f);
}
처음 렌더는 캔버스의 배경 -> 회색 영역 -> 파란색 영역 -> thumb 로 해서 z-index도 부여해줬다. 대충 흰 배경과 회색 영역이 있고 그 위에서 파란 영역과 thumb가 움직인다고 생각하면 된다.
bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
//background
auto bg = tvg::Shape::gen();
bg->appendRect(0, 0, w, h);
bg->fill(248, 248, 248);
canvas->add(bg);
//the empty groove
auto groove = tvg::Shape::gen();
track(groove, TRACK_W);
groove->fill(214, 214, 218);
canvas->add(groove);
//the blue progress fill (rebuilt by apply(); the gradient is retained)
blue = tvg::Shape::gen();
auto grad = tvg::LinearGradient::gen();
grad->linear(TRACK_X, 0, TRACK_X + TRACK_W, 0);
tvg::Fill::ColorStop stops[2];
stops[0] = {0.0f, 96, 150, 245, 255};
stops[1] = {1.0f, 56, 108, 240, 255};
grad->colorStops(stops, 2);
blue->fill(grad);
canvas->add(blue);
//the thumb: a white pill with a soft drop shadow, slid via translate
thumb = tvg::Scene::gen();
auto pill = tvg::Shape::gen();
pill->appendRect(TRACK_X - THUMB_W * 0.5f, TRACK_CY - THUMB_H * 0.5f, THUMB_W, THUMB_H, THUMB_H * 0.5f, THUMB_H * 0.5f);
pill->fill(255, 255, 255);
thumb->add(pill);
thumb->add(tvg::SceneEffect::DropShadow, 0, 30, 63, 26, 90.0, 1.5, 6.0, 50); //rgba, angle, dist, sigma, quality
canvas->add(thumb);
apply();
return true;
}
파란 영역과 thumb의 위치 계산 함수. value는 슬라이더의 0-1 사이의 값이고 이를 너비랑 곱해서 thumb 위치 및 파란 영역이 어느정도 있어야 하는지 결정한다.
//Reposition the blue fill and the thumb for the current `value`.
void apply()
{
auto dx = value * TRACK_W;
blue->reset(); //keeps the gradient fill
track(blue, dx);
thumb->translate(dx, 0);
}
트랙 영역이 클릭되고 드래깅 된다며 해당 위치를 통해 value를 계산, apply 및 track 함수를 통해 파란 영역과 thumb의 위치를 지정한다. 결국 동작 자체는 thumb의 클릭과 상관 없이 트랙 영역이 클릭된다면 해당 위치를 기반으로 thumb를 이동시키고 파란 영역 값을 재계산 한다고 생각하면 된다.
//Map a mouse x onto the track. Returns false if (x, y) is off the slider.
bool grab(int32_t x, int32_t y)
{
if (y < (int)(TRACK_CY - THUMB_H) || y > (int)(TRACK_CY + THUMB_H)) return false; //generous vertical slop
if (x < (int)(TRACK_X - THUMB_W * 0.5f) || x > (int)(TRACK_X + TRACK_W + THUMB_W * 0.5f)) return false;
auto v = ((float)x - TRACK_X) / TRACK_W;
value = (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
return true;
}
bool clickdown(tvg::Canvas* canvas, int32_t x, int32_t y) override
{
if (!grab(x, y)) return false;
dragging = true;
apply();
canvas->update();
return true;
}
bool motion(tvg::Canvas* canvas, int32_t x, int32_t y) override
{
if (!dragging) return false;
auto prev = value;
grab(x, (int32_t)TRACK_CY); //track horizontally even if the cursor drifts
if (value == prev) return false; //ignore no-op moves
apply();
canvas->update();
return true;
}
bool clickup(tvg::Canvas* canvas, int32_t x, int32_t y) override
{
dragging = false;
return false;
}
결과
글래스모피즘을 구현하지 못해서 아쉽지만 그래도 적당히 이쁘게 나온 것 같아서 만족합니다.
댓글
Discussion 원문