#include #include namespace psemek::ui { void rich_image_view::set_image(std::shared_ptr image) { image_ = std::move(image); post_reshape(); } void rich_image_view::set_zoom_range(geom::interval range) { float z = zoom(); zoom_range_ = range; set_zoom(z); } void rich_image_view::set_center(geom::point const & center) { center_ = center; if (!allow_overflow_) { float const w = shape_.box[0].length() / zoom_ / 2.f; float const h = shape_.box[1].length() / zoom_ / 2.f; geom::box b; b[0] = {w, image_->width() - w}; b[1] = {h, image_->height() - h}; if (b[0].empty()) std::swap(b[0].min, b[0].max); if (b[1].empty()) std::swap(b[1].min, b[1].max); center_ = geom::clamp(center_, b); } post_region_changed(); } void rich_image_view::set_zoom(float zoom) { zoom = geom::clamp(zoom, zoom_range_); // Screen -> Texcoords // mouse |-> (mouse - bbox.center) / zoom + center // (mouse - bbox.center) / zoom0 + center0 = (mouse - bbox.center) / zoom1 + center1 // center1 - center0 = (mouse - bbox.center) * (1 / zoom0 - 1 / zoom1) zoom_tgt_ = zoom; zoom_ = zoom; set_center(center_); } geom::box rich_image_view::region() const { float const w = shape_.box[0].length() / zoom_ / 2.f; float const h = shape_.box[1].length() / zoom_ / 2.f; geom::box r; r[0] = {center_[0] - w, center_[0] + w}; r[1] = {center_[1] - h, center_[1] + h}; return r; } void rich_image_view::set_allow_overflow(bool value) { allow_overflow_ = value; set_center(center_); } bool rich_image_view::on_event(mouse_move const & e) { mouseover_ = geom::contains(shape_.box, geom::cast(e.position)); mouse_ = e.position; if (drag_) { set_center(center_ + geom::cast(*drag_ - e.position) / zoom_); drag_ = e.position; } return false; } bool rich_image_view::on_event(mouse_click const & e) { if (e.button == mouse_button::right) { if (e.down && mouse_ && mouseover_) { drag_ = *mouse_; return true; } else if (!e.down) { drag_ = std::nullopt; return mouseover_; } } return false; } bool rich_image_view::on_event(mouse_wheel const & e) { if (mouseover_) { zoom_tgt_ *= std::pow(1.25f, e.delta); zoom_tgt_ = geom::clamp(zoom_tgt_, zoom_range_); set_center(center_); return true; } return false; } bool rich_image_view::on_event(key_press const & e) { if (mouseover_ && e.down) { if (e.key == SDLK_KP_PLUS) { on_event(mouse_wheel{1}); return true; } if (e.key == SDLK_KP_MINUS) { on_event(mouse_wheel{-1}); return true; } } return false; } void rich_image_view::reshape(geom::box const & bbox) { shape_.box = bbox; post_region_changed(); } void rich_image_view::update(float dt) { float new_zoom = zoom_ + (zoom_tgt_ - zoom_) * std::min(1.f, dt * 20.f); auto new_center = center_; if (mouse_) new_center += (geom::cast(*mouse_) - shape_.box.center()) * (1.f / zoom_ - 1.f / new_zoom); zoom_ = new_zoom; set_center(new_center); } void rich_image_view::draw(painter & p) const { auto st = merged_style(); if (image_) { auto box = shape_.box; auto reg = region(); if (!allow_overflow_) { auto new_reg = reg; new_reg[0].min = std::max(0.f, new_reg[0].min); new_reg[1].min = std::max(0.f, new_reg[1].min); new_reg[0].max = std::min(image_->width(), new_reg[0].max); new_reg[1].max = std::min(image_->height(), new_reg[1].max); auto new_box = box; new_box[0].min = geom::lerp(box[0], geom::unlerp(reg[0], new_reg[0].min)); new_box[0].max = geom::lerp(box[0], geom::unlerp(reg[0], new_reg[0].max)); new_box[1].min = geom::lerp(box[1], geom::unlerp(reg[1], new_reg[1].min)); new_box[1].max = geom::lerp(box[1], geom::unlerp(reg[1], new_reg[1].max)); box = new_box; reg = new_reg; } if (*st->shadow_offset != geom::vector{0, 0}) p.draw_rect(box + geom::cast(*st->shadow_offset), *st->shadow_color); p.draw_subimage(box, *image_, reg, color_); } } void rich_image_view::post_region_changed() { auto weak_self = std::weak_ptr{std::dynamic_pointer_cast(shared_from_this())}; post([weak_self]{ auto self = weak_self.lock(); if (!self) return; self->on_region_changed(); }); } }