text: // Released under the MIT License. See LICENSE for details.
  
  #include "ballistica/ui/widget/h_scroll_widget.h"
  
  #include "ballistica/generic/real_timer.h"
  #include "ballistica/graphics/component/empty_component.h"
  #include "ballistica/graphics/component/simple_component.h"
  #include "ballistica/ui/ui.h"
  
  namespace ballistica {
  
  const float kHMargin = 5.0f;
  
  HScrollWidget::HScrollWidget()
      : touch_mode_(!g_platform->IsRunningOnDesktop()) {
    set_draggable(false);
    set_claims_left_right(false);
    set_claims_tab(false);
  }
  
  HScrollWidget::~HScrollWidget() = default;
  
  void HScrollWidget::HandleRealTimerExpired(RealTimer<HScrollWidget>* t) {
    if (touch_held_) {
      // Pass a mouse-down event if we haven't moved.
      if (!touch_is_scrolling_ && !touch_down_sent_) {
        ContainerWidget::HandleMessage(
            WidgetMessage(WidgetMessage::Type::kMouseDown, nullptr, touch_x_,
                          touch_y_, static_cast<float>(touch_held_click_count_)));
        touch_down_sent_ = true;
      } else {
      }
    }
  
    // Clean ourself out.
    touch_delay_timer_.Clear();
  }
  
  void HScrollWidget::ClampThumb(bool velocity_clamp, bool position_clamp) {
    BA_DEBUG_UI_READ_LOCK;
    bool is_scrolling = (touch_held_ || !has_momentum_);
    float strong_force;
    float weak_force;
    if (touch_mode_) {
      strong_force = -0.12f;
      weak_force = -0.004f;
    } else {
      strong_force = -0.012f;
      weak_force = -0.004f;
    }
    auto i = widgets().begin();
    if (i != widgets().end()) {
      float child_w = (**i).GetWidth();
  
      if (velocity_clamp) {
        if (child_offset_h_ < 0) {
          // even in velocity case do some sane clamping
          float diff = child_offset_h_;
          inertia_scroll_rate_ +=
              diff * (is_scrolling ? strong_force : weak_force);
          inertia_scroll_rate_ *= 0.9f;
  
        } else if (child_offset_h_
                   > child_w - (width() - 2 * (border_width_ + kHMargin))) {
          float diff =
              child_offset_h_
              - (child_w
                 - std::min(child_w, (width() - 2 * (border_width_ + kHMargin))));
          inertia_scroll_rate_ +=
              diff * (is_scrolling ? strong_force : weak_force);
          inertia_scroll_rate_ *= 0.9f;
        }
      }
  
      // hard clipping if we're dragging the scrollbar
      if (position_clamp) {
        if (child_offset_h_smoothed_
            > child_w - (width() - 2 * (border_width_ + kHMargin))) {
          child_offset_h_smoothed_ =
              child_w - (width() - 2 * (border_width_ + kHMargin));
        }
        if (child_offset_h_smoothed_ < 0) {
          child_offset_h_smoothed_ = 0;
        }
        if (child_offset_h_
            > child_w - (width() - 2 * (border_width_ + kHMargin))) {
          child_offset_h_ = child_w - (width() - 2 * (border_width_ + kHMargin));
        }
        if (child_offset_h_ < 0) {
          child_offset_h_ = 0;
        }
      }
    }
  }
  
  auto HScrollWidget::HandleMessage(const WidgetMessage& m) -> bool {
    BA_DEBUG_UI_READ_LOCK;
    bool claimed = false;
    bool pass = true;
    float bottom_overlap = 3;
    switch (m.type) {
      case WidgetMessage::Type::kShow: {
        claimed = true;
        pass = false;
        auto i = widgets().begin();
        if (i == widgets().end()) break;
        float child_w = (**i).GetWidth();
  
        // see where we'd have to scroll to get selection at left and right
        float child_offset_left =
            child_w - m.fval1 - (width() - 2 * (border_width_ + kHMargin));
        float child_offset_right = child_w - m.fval1 - m.fval3;
  
        // if we're in the middle, dont do anything
        if (child_offset_h_ > child_offset_left
            && child_offset_h_ < child_offset_right) {
        } else {
          float prev_child_offset = child_offset_h_;
          // do whatever offset is less of a move
          if (std::abs(child_offset_left - child_offset_h_)
              < std::abs(child_offset_right - child_offset_h_)) {
            child_offset_h_ = child_offset_left;
          } else {
            child_offset_h_ = child_offset_right;
          }
  
          // if we're moving left, stop at the end
          {
            float max_val = child_w - (width() - 2 * (border_width_ + kHMargin));
            if (child_offset_h_ > max_val) child_offset_h_ = max_val;
          }
          // if we're moving right, stop at the top
          {
            if (child_offset_h_ < prev_child_offset) {
              if (child_offset_h_ < 0) child_offset_h_ = 0;
            }
          }
        }
  
        // Go into smooth mode momentarily.
        smoothing_amount_ = 1.0f;
  
        // Snap our smoothed value to this *only* if we haven't drawn yet
        // (keeps new widgets from inexplicably scrolling around).
        if (!have_drawn_) {
          child_offset_h_smoothed_ = child_offset_h_;
        }
        MarkForUpdate();
        break;
      }
      case WidgetMessage::Type::kMouseMove: {
        float x = m.fval1;
        float y = m.fval2;
        bool claimed2 = (m.fval3 > 0.0f);
  
        if (touch_mode_) {
          mouse_over_ = false;
        } else {
          mouse_over_ =
              ((y >= 0.0f) && (y < height()) && (x >= 0.0f) && (x < width()));
        }
  
        if (!mouse_over_) {
          pass = false;
        }
  
        if (claimed2) {
          mouse_over_thumb_ = false;
        } else {
          if (touch_mode_) {
            if (touch_held_) {
              touch_x_ = x;
              touch_y_ = y;
  
              // if this is a new scroll-touch, see which direction the drag is
              // happening; if it's primarily vertical lets disown it so it can
              // get handled by the scroll widget above us (presumably a vertical
              // scroll widget)
              if (new_scroll_touch_) {
                float x_diff = std::abs(touch_x_ - touch_start_x_);
                float y_diff = std::abs(touch_y_ - touch_start_y_);
  
                float dist = x_diff * x_diff + y_diff * y_diff;
  
                // if they're somehow equal, wait and look at the next one..
                if (x_diff != y_diff && dist > 30.0f) {
                  new_scroll_touch_ = false;
  
                  // If they haven't moved far enough yet, ignore it.
                  if (x_diff < y_diff) {
                    return false;
                  }
                }
              }
  
              // Handle generating delayed press/releases.
              if (static_cast<int>(m.type)) {  // <- FIXME WHAT IS THIS FOR??
                // If we move more than a slight amount it means our touch isn't a
                // click.
                if (!touch_is_scrolling_
                    && ((std::abs(touch_x_ - touch_start_x_) > 10.0f)
                        || (std::abs(touch_y_ - touch_start_y_) > 10.0f))) {
                  touch_is_scrolling_ = true;
  
                  // Go ahead and send a mouse-up to the sub-widgets; in their
                  // eyes the click is canceled.
                  if (touch_down_sent_ && !touch_up_sent_) {
                    ContainerWidget::HandleMessage(
                        WidgetMessage(WidgetMessage::Type::kMouseUp, nullptr,
                                      m.fval1, m.fval2, true));
                    touch_up_sent_ = true;
                  }
                }
              }
              return true;
            }
          }
  
          if (touch_mode_) {
            mouse_over_thumb_ = false;
          } else {
            float s_right = width() - border_width_;
            float s_left = border_width_;
            float sb_thumb_width =
                amount_visible_ * (width() - 2.0f * border_width_);
            float sb_thumb_right = s_right
                                   - child_offset_h_ / child_max_offset_
                                         * (s_right - (s_left + sb_thumb_width));
  
            mouse_over_thumb_ =
                (((y >= 0) && (y < scroll_bar_height_ + bottom_overlap)
                  && x < sb_thumb_right && x >= sb_thumb_right - sb_thumb_width));
          }
        }
  
        // If we're dragging.
        if (mouse_held_thumb_) {
          auto i = widgets().begin();
          if (i == widgets().end()) {
            break;
          }
          float child_w = (**i).GetWidth();
          float sRight = width() - border_width_;
          float sLeft = border_width_;
          float rate =
              (child_w - (sRight - sLeft))
              / ((1.0f - ((sRight - sLeft) / child_w)) * (sRight - sLeft));
          child_offset_h_ = thumb_click_start_child_offset_h_
                            - rate * (x - thumb_click_start_h_);
  
          ClampThumb(false, true);
  
          MarkForUpdate();
        }
        break;
      }
      case WidgetMessage::Type::kMouseUp: {
        mouse_held_scroll_down_ = false;
        mouse_held_scroll_up_ = false;
        mouse_held_thumb_ = false;
        mouse_held_page_down_ = false;
        mouse_held_page_up_ = false;
  
        if (touch_mode_) {
          if (touch_held_) {
            bool m_claimed = (m.fval3 > 0.0f);
  
            touch_held_ = false;
  
            // If we moved at all, we mark it as claimed to keep
            // sub-widgets from acting on it (since we used it for scrolling).
            bool claimed2 = touch_is_scrolling_ || m_claimed;
  
            // If we're not claiming it and we haven't sent a mouse_down yet due
            // to our delay, send that first.
            if (!claimed2 && !touch_down_sent_) {
              ContainerWidget::HandleMessage(WidgetMessage(
                  WidgetMessage::Type::kMouseDown, nullptr, m.fval1, m.fval2,
                  static_cast<float>(touch_held_click_count_)));
              touch_down_sent_ = true;
            }
            if (touch_down_sent_ && !touch_up_sent_) {
              ContainerWidget::HandleMessage(
                  WidgetMessage(WidgetMessage::Type::kMouseUp, nullptr, m.fval1,
                                m.fval2, claimed2));
              touch_up_sent_ = true;
            }
            return true;
          }
        }
  
        // If coords are outside of our bounds, pass a mouse-up along for anyone
        // tracking a drag, but mark it as claimed so it doesn't actually get
        // acted on.
        float x = m.fval1;
        float y = m.fval2;
        if (!((y >= 0.0f) && (y < height()) && (x >= 0.0f) && (x < width()))) {
          pass = false;
          ContainerWidget::HandleMessage(WidgetMessage(
              WidgetMessage::Type::kMouseUp, nullptr, m.fval1, m.fval2, true));
        }
  
        break;
      }
  
      case WidgetMessage::Type::kMouseWheelVelocityH: {
        float x = m.fval1;
        float y = m.fval2;
        if ((x >= 0.0f) && (x < width()) && (y >= 0.0f) && (y < height())) {
          claimed = true;
          pass = false;
          has_momentum_ = static_cast<bool>(m.fval4);
  
          // We only set velocity from events when not in momentum mode; we
          // handle momentum ourself.
          if (std::abs(m.fval3) > 0.001f && !has_momentum_) {
            float scroll_speed = 2.2f;
            float smoothing = 0.8f;
            float new_val;
            if (m.fval3 < 0.0f) {
              // Apply less if we're past the end.
              if (child_offset_h_ < 0) {
                new_val = scroll_speed * 0.1f * m.fval3;
              } else {
                new_val = scroll_speed * m.fval3;
              }
            } else {
              // Apply less if we're past the end.
              bool past_end = false;
  
              // Calc our total height.
              auto i = widgets().begin();
              if (i != widgets().end()) {
                float child_h = (**i).GetWidth();
                float diff =
                    child_offset_h_
                    - (child_h
                       - std::min(child_h,
                                  (width() - 2 * (border_width_ + kHMargin))));
                if (diff > 0) past_end = true;
              }
              if (past_end) {
                new_val = scroll_speed * 0.1f * m.fval3;
              } else {
                new_val = scroll_speed * m.fval3;
              }
            }
            inertia_scroll_rate_ =
                smoothing * inertia_scroll_rate_ + (1.0f - smoothing) * new_val;
          }
          last_velocity_event_time_ = g_game->master_time();
          MarkForUpdate();
        } else {
          // Not within our widget; dont allow children to claim.
          pass = false;
        }
        break;
      }
      case WidgetMessage::Type::kMouseWheelH: {
        float x = m.fval1;
        float y = m.fval2;
        if ((x >= 0.0f) && (x < width()) && (y >= 0.0f) && (y < height())) {
          claimed = true;
          pass = false;
          inertia_scroll_rate_ -= m.fval3 * 0.003f;
          MarkForUpdate();
        } else {
          // Not within our widget; dont allow children to claim.
          pass = false;
        }
        break;
      }
      case WidgetMessage::Type::kScrollMouseDown:
      case WidgetMessage::Type::kMouseDown: {
        float x = m.fval1;
        float y = m.fval2;
  
        // If its in our overall scroll region at all.
        if ((y >= 0.0f) && (y < height()) && (x >= 0.0f) && (x < width())) {
          // On touch devices, clicks begin scrolling, (and eventually can count
          // as clicks if they don't move)
          if (touch_mode_) {
            touch_held_ = true;
            auto click_count = static_cast<int>(m.fval3);
            touch_held_click_count_ = click_count;
            touch_down_sent_ = false;
            touch_up_sent_ = false;
            touch_start_x_ = x;
            touch_start_y_ = y;
            touch_x_ = x;
            touch_y_ = y;
            touch_down_x_ = x - child_offset_h_;
            touch_is_scrolling_ = false;
  
            // If there's significant scrolling happening we never pass touches.
            // they're only used to scroll more/less.
            if (std::abs(inertia_scroll_rate_) > 0.05f) {
              touch_is_scrolling_ = true;
            }
  
            pass = false;
            claimed = true;
  
            // Top level touches eventually get passed as mouse-downs if no
            // scrolling has started.
            if (static_cast<int>(m.type)) {
              touch_delay_timer_ =
                  Object::New<RealTimer<HScrollWidget>>(150, false, this);
            }
  
            // If we're handling a scroll-touch, take note that we need to
            // decide whether to disown the touch or not.
            if (m.type == WidgetMessage::Type::kScrollMouseDown) {
              new_scroll_touch_ = true;
            }
          }
  
          // On desktop, allow clicking on the scrollbar.
          if (!touch_mode_) {
            if (y <= scroll_bar_height_ + bottom_overlap) {
              claimed = true;
              pass = false;
  
              float sRight = width() - border_width_;
              float sLeft = border_width_;
              float sb_thumb_width =
                  amount_visible_ * (width() - 2 * border_width_);
              float sb_thumb_right = sRight
                                     - child_offset_h_ / child_max_offset_
                                           * (sRight - (sLeft + sb_thumb_width));
  
              // To right of thumb (page-right).
              if (x >= sb_thumb_right) {
                smoothing_amount_ = 1.0f;  // So we can see the transition.
                child_offset_h_ -= (width() - 2 * (border_width_ + kHMargin));
                MarkForUpdate();
                ClampThumb(false, true);
              } else if (x >= sb_thumb_right - sb_thumb_width) {
                // On thumb.
                mouse_held_thumb_ = true;
                thumb_click_start_h_ = x;
                thumb_click_start_child_offset_h_ = child_offset_h_;
              } else if (x >= sLeft) {
                // To left of thumb (page left).
                smoothing_amount_ = 1.0f;  // So we can see the transition.
                child_offset_h_ += (width() - 2 * (border_width_ + kHMargin));
                MarkForUpdate();
                ClampThumb(false, true);
              }
            }
          }
        } else {
          pass = false;  // Not in the scroll box; dont allow children to claim.
        }
        break;
      }
      default:
        break;
    }
  
    // Normal container event handling.
    if (pass) {
      if (ContainerWidget::HandleMessage(m)) claimed = true;
    }
  
    // If it was a mouse-down and we claimed it, set ourself as selected.
    if (m.type == WidgetMessage::Type::kMouseDown && claimed) {
      GlobalSelect();
    }
    return claimed;
  }
  
  void HScrollWidget::UpdateLayout() {
    BA_DEBUG_UI_READ_LOCK;
  
    // Move everything based on our offset.
    auto i = widgets().begin();
    if (i == widgets().end()) {
      amount_visible_ = 0;
      return;
    }
    float child_w = (**i).GetWidth();
    child_max_offset_ = child_w - (width() - 2 * (border_width_ + kHMargin));
    amount_visible_ = (width() - 2 * (border_width_ + kHMargin)) / child_w;
    if (amount_visible_ > 1) {
      amount_visible_ = 1;
      if (center_small_content_) {
        center_offset_x_ = child_max_offset_ * 0.5f;
      } else {
        center_offset_x_ = 0;
      }
    } else {
      center_offset_x_ = 0;
    }
    if (mouse_held_thumb_) {
      if (child_offset_h_
          > child_w - (width() - 2 * (border_width_ + kHMargin))) {
        child_offset_h_ = child_w - (width() - 2 * (border_width_ + kHMargin));
        inertia_scroll_rate_ = 0;
      }
      if (child_offset_h_ < 0) {
        child_offset_h_ = 0;
        inertia_scroll_rate_ = 0;
      }
    }
    (**i).set_translate(width() - (border_width_ + kHMargin)
                            + child_offset_h_smoothed_ - child_w
                            + center_offset_x_,
                        4 + border_height_);
    thumb_dirty_ = true;
  }
  
  void HScrollWidget::Draw(RenderPass* pass, bool draw_transparent) {
    have_drawn_ = true;
    millisecs_t current_time = pass->frame_def()->base_time();
    float prev_child_offset_h_smoothed = child_offset_h_smoothed_;
  
    // Ok, lets update our inertial scrolling during the opaque pass.
    // (we really should have some sort of update() function for this but widgets
    // don't have that currently)
    if (!draw_transparent) {
      // (skip huge differences)
      if (current_time - inertia_scroll_update_time_ > 1000)
        inertia_scroll_update_time_ = current_time - 1000;
      while (current_time - inertia_scroll_update_time_ > 5) {
        inertia_scroll_update_time_ += 5;
  
        if (touch_mode_) {
          if (touch_held_) {
            float diff = (touch_x_ - child_offset_h_) - touch_down_x_;
            float smoothing = 0.7f;
            inertia_scroll_rate_ = smoothing * inertia_scroll_rate_
                                   + (1.0f - smoothing) * 0.2f * diff;
          } else {
            inertia_scroll_rate_ *= 0.98f;
          }
        } else {
          inertia_scroll_rate_ *= 0.98f;
        }
  
        ClampThumb(true, mouse_held_thumb_);
        child_offset_h_ += inertia_scroll_rate_;
  
        if (!has_momentum_
            && (current_time - last_velocity_event_time_ > 1000 / 30)) {
          inertia_scroll_rate_ = 0;
        }
  
        // Lastly we apply smoothing so that if we're snapping to a specific place
        // we don't go instantly there we blend between smoothed and non-smoothed
        // depending on whats driving us (we dont want to add smoothing on top of
        // inertial scrolling for example or it'll feel muddy)
        float diff = child_offset_h_ - child_offset_h_smoothed_;
        if (std::abs(diff) < 1.0f)
          child_offset_h_smoothed_ = child_offset_h_;
        else
          child_offset_h_smoothed_ += (1.0f - 0.95f * smoothing_amount_) * diff;
        smoothing_amount_ = std::max(0.0f, smoothing_amount_ - 0.005f);
      }
  
      // Only re-layout our widgets if we've moved a significant amount.
      if (std::abs(prev_child_offset_h_smoothed - child_offset_h_smoothed_)
          > 0.01f) {
        MarkForUpdate();
      }
    }
  
    CheckLayout();
  
    Vector3f tilt = 0.02f * g_graphics->tilt();
    float extra_offs_x = tilt.y;
    float extra_offs_y = -tilt.x;
  
    float b = 0;
    float t = b + height();
    float l = 0;
    float r = l + width();
  
    // Begin clipping for children.
    {
      EmptyComponent c(pass);
      c.SetTransparent(draw_transparent);
      c.ScissorPush(Rect(l + border_width_, b + border_height_ + 1,
                         l + (width() - border_width_ - 0),
                         b + (height() - border_height_) - 1));
      c.Submit();
    }
  
    set_simple_culling_left(l + border_width_);
    set_simple_culling_right(l + (width() - border_height_));
  
    // Draw all our widgets at our z level.
    DrawChildren(pass, draw_transparent, l + extra_offs_x, b + extra_offs_y,
                 1.0f);
  
    // End clipping.
    {
      EmptyComponent c(pass);
      c.SetTransparent(draw_transparent);
      c.ScissorPop();
      c.Submit();
    }
  
    // scroll trough (depth 0.7f to 0.8f)
    if (draw_transparent && border_opacity_ > 0.0f) {
      if (trough_dirty_) {
        float b2 = b + 4;
        float t2 = b2 + scroll_bar_height_;
        float l2;
        float r2;
        l2 = l + (border_width_);
        r2 = r - (border_width_);
        float b_border, t_border, l_border, r_border;
        b_border = 3;
        t_border = 0;
        l_border = width() * 0.006f;
        r_border = width() * 0.002f;
        trough_height_ = t2 - b2 + b_border + t_border;
        trough_width_ = r2 - l2 + l_border + r_border;
        trough_center_y_ = b2 - b_border + trough_height_ * 0.5f;
        trough_center_x_ = l2 - l_border + trough_width_ * 0.5f;
        trough_dirty_ = false;
      }
  
      SimpleComponent c(pass);
      c.SetTransparent(true);
      c.SetColor(1, 1, 1, border_opacity_);
      c.SetTexture(g_media->GetTexture(SystemTextureID::kUIAtlas));
      c.PushTransform();
      c.Translate(trough_center_x_, trough_center_y_, 0.7f);
      c.Scale(trough_width_, trough_height_, 0.1f);
      c.Rotate(-90, 0, 0, 1);
      c.DrawModel(g_media->GetModel(SystemModelID::kScrollBarTroughTransparent));
      c.PopTransform();
      c.Submit();
    }
  
    // scroll bars
    if (amount_visible_ > 0 && amount_visible_ < 1) {
      // scroll thumb at depth 0.8f-0.9
      {
        float sb_thumb_width = amount_visible_ * (width() - 2 * border_width_);
        if (thumb_dirty_) {
          float sb_thumb_right =
              r - border_width_
              - ((width() - (border_width_ * 2) - sb_thumb_width)
                 * child_offset_h_smoothed_ / child_max_offset_);
          float b2 = 4;
          float t2 = b2 + scroll_bar_height_;
          float r2 = sb_thumb_right;
          float l2 = r2 - sb_thumb_width;
          float b_border, t_border, l_border, r_border;
          b_border = 6;
          t_border = 3;
          if (sb_thumb_width > 100) {
            auto wd = r2 - l2;
            l_border = wd * 0.04f;
            r_border = wd * 0.06f;
          } else {
            auto wd = r2 - l2;
            r_border = wd * 0.12f;
            l_border = wd * 0.08f;
          }
          thumb_height_ = t2 - b2 + b_border + t_border;
          thumb_width_ = r2 - l2 + l_border + r_border;
  
          thumb_center_y_ = b2 - b_border + thumb_height_ * 0.5f;
          thumb_center_x_ = l2 - l_border + thumb_width_ * 0.5f;
          thumb_dirty_ = false;
        }
  
        SimpleComponent c(pass);
        c.SetTransparent(draw_transparent);
        //      float c_scale = 1.0f;
        //      if (mouse_held_thumb_) {
        //        c_scale = 1.8f;
        //      } else if (mouse_over_thumb_) {
        //        c_scale = 1.25f;
        //      }
  
        bool smooth_diff =
            (std::abs(child_offset_h_smoothed_ - child_offset_h_) > 0.01f);
        if (touch_mode_) {
          if (smooth_diff || (touch_held_ && touch_is_scrolling_)
              || std::abs(inertia_scroll_rate_) > 1.0f) {
            touch_fade_ = std::min(1.5f, touch_fade_ + 0.02f);
          } else {
            // FIXME: Shouldn't be frame based.
            touch_fade_ = std::max(0.0f, touch_fade_ - 0.015f);
          }
        } else {
          if (smooth_diff || (touch_held_ && touch_is_scrolling_)
              || std::abs(inertia_scroll_rate_) > 1.0f || mouse_over_) {
            touch_fade_ = std::min(1.5f, touch_fade_ + 0.02f);
          } else {
            // FIXME: Shouldn't be frame based.
            touch_fade_ = std::max(0.0f, touch_fade_ - 0.015f);
          }
        }
        c.SetColor(0, 0, 0, std::min(1.0f, 0.3f * touch_fade_));
  
        c.ScissorPush(Rect(l + border_width_, b + border_height_ + 1,
                           l + (width()), b + (height() * 0.995f)));
        c.PushTransform();
        c.Translate(thumb_center_x_, thumb_center_y_, 0.8f);
        c.Scale(-thumb_width_, thumb_height_, 0.1f);
        c.FlipCullFace();
        c.Rotate(-90, 0, 0, 1);
  
        // on touch, just draw these transiently
  #if 1
        if (draw_transparent) {
          c.DrawModel(g_media->GetModel(
              sb_thumb_width > 100 ? SystemModelID::kScrollBarThumbSimple
                                   : SystemModelID::kScrollBarThumbShortSimple));
        }
  #else
        if (draw_transparent) {
          c.DrawModel(g_media->GetModel(
              sb_thumb_width > 100
                  ? Media::SCROLL_BAR_THUMB_TRANSPARENT_MODEL
                  : Media::SCROLL_BAR_THUMB_SHORT_TRANSPARENT_MODEL));
        } else {
          c.DrawModel(g_media->GetModel(
              sb_thumb_width > 100 ? Media::SCROLL_BAR_THUMB_OPAQUE_MODEL
                                   : Media::SCROLL_BAR_THUMB_SHORT_OPAQUE_MODEL));
        }
  #endif
  
        c.FlipCullFace();
        c.PopTransform();
        c.ScissorPop();
        c.Submit();
      }
    }
  
    // outline shadow (depth 0.9 to 1.0)
    if (draw_transparent && border_opacity_ > 0.0f) {
      if (shadow_dirty_) {
        float r2 = l + width();
        float l2 = l;
        float b2 = b;
        float t2 = t;
        float l_border, r_border, b_border, t_border;
        l_border = (r2 - l2) * 0.005f;
        r_border = (r2 - l2) * 0.001f;
        b_border = (t2 - b2) * 0.006f;
        t_border = (t2 - b2) * 0.002f;
        outline_width_ = r2 - l2 + l_border + r_border;
        outline_height_ = t2 - b2 + b_border + t_border;
        outline_center_x_ = l2 - l_border + 0.5f * outline_width_;
        outline_center_y_ = b2 - b_border + 0.5f * outline_height_;
        shadow_dirty_ = false;
      }
      SimpleComponent c(pass);
      c.SetTransparent(true);
      c.SetColor(1, 1, 1, border_opacity_);
      c.SetTexture(g_media->GetTexture(SystemTextureID::kScrollWidget));
      c.PushTransform();
      c.Translate(outline_center_x_, outline_center_y_, 0.9f);
      c.Scale(outline_width_, outline_height_, 0.1f);
      c.DrawModel(g_media->GetModel(SystemModelID::kSoftEdgeOutside));
      c.PopTransform();
      c.Submit();
    }
  
    // if selected, do glow at depth 0.9-1.0
    if (draw_transparent && IsHierarchySelected()
        && g_ui->ShouldHighlightWidgets() && highlight_
        && border_opacity_ > 0.0f) {
      float m = 0.8f
                + std::abs(sinf(static_cast<float>(current_time) * 0.006467f))
                      * 0.2f * border_opacity_;
  
      if (glow_dirty_) {
        float r2 = l + width();
        float l2 = l;
        float b2 = b;
        float t2 = t;
        float l_border, r_border, b_border, t_border;
        l_border = (r2 - l2) * 0.02f;
        r_border = (r2 - l2) * 0.02f;
        b_border = (t2 - b2) * 0.015f;
        t_border = (t2 - b2) * 0.01f;
        glow_width_ = r2 - l2 + l_border + r_border;
        glow_height_ = t2 - b2 + b_border + t_border;
        glow_center_x_ = l2 - l_border + 0.5f * glow_width_;
        glow_center_y_ = b2 - b_border + 0.5f * glow_height_;
        glow_dirty_ = false;
      }
      SimpleComponent c(pass);
      c.SetTransparent(true);
      c.SetPremultiplied(true);
      c.SetColor(0.4f * m, 0.5f * m, 0.05f * m, 0.0f);
      c.SetTexture(g_media->GetTexture(SystemTextureID::kScrollWidgetGlow));
      c.PushTransform();
      c.Translate(glow_center_x_, glow_center_y_, 0.9f);
      c.Scale(glow_width_, glow_height_, 0.1f);
      c.DrawModel(g_media->GetModel(SystemModelID::kSoftEdgeOutside));
      c.PopTransform();
      c.Submit();
    }
  }
  
  }  // namespace ballistica
  