// Filename: pgSliderBar.cxx
// Created by:  masad (19Oct04)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "pgSliderBar.h"
#include "pgMouseWatcherParameter.h"
#include "clockObject.h"
#include "throw_event.h"
#include "config_pgui.h"
#include "throw_event.h"
#include "transformState.h"
#include "mouseButton.h"

TypeHandle PGSliderBar::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
PGSliderBar::
PGSliderBar(const string &name) 
  : PGItem(name)
{
  _min_value = 0.0f;
  _max_value = 1.0f;
  set_scroll_size(0.01f);
  set_page_size(0.1f);
  _ratio = 0.0f;
  _resize_thumb = false;
  _manage_pieces = false;
  _axis.set(1.0f, 0.0f, 0.0f);
  _needs_remanage = false;
  _needs_recompute = true;
  _needs_reposition = false;
  _scroll_button_held = NULL;
  _mouse_button_page = false;
  _dragging = false;
  _thumb_width = 0.1f;

  set_active(true);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
PGSliderBar::
~PGSliderBar() {
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::Copy Constructor
//       Access: Protected
//  Description: 
////////////////////////////////////////////////////////////////////
PGSliderBar::
PGSliderBar(const PGSliderBar &copy) :
  PGItem(copy),
  _min_value(copy._min_value),
  _max_value(copy._max_value),
  _scroll_value(copy._scroll_value),
  _scroll_ratio(copy._scroll_ratio),
  _page_value(copy._page_value),
  _page_ratio(copy._page_ratio),
  _ratio(copy._ratio),
  _resize_thumb(copy._resize_thumb),
  _manage_pieces(copy._manage_pieces),
  _axis(copy._axis)
{
  _needs_remanage = false;
  _needs_recompute = true;
  _scroll_button_held = NULL;
  _mouse_button_page = false;
  _dragging = false;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::make_copy
//       Access: Public, Virtual
//  Description: Returns a newly-allocated Node that is a shallow copy
//               of this one.  It will be a different Node pointer,
//               but its internal data may or may not be shared with
//               that of the original Node.
////////////////////////////////////////////////////////////////////
PandaNode *PGSliderBar::
make_copy() const {
  return new PGSliderBar(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::press
//       Access: Public, Virtual
//  Description: This is a callback hook function, called whenever a
//               mouse or keyboard button is depressed while the mouse
//               is within the region.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
press(const MouseWatcherParameter &param, bool background) {
  if (param.has_mouse()) {
    _mouse_pos = param.get_mouse();
  }
  if (get_active() && param.get_button() == MouseButton::one()) {
    if (_needs_recompute) {
      recompute();
    }

    if (_range_x != 0.0f) {
      _mouse_button_page = true;
      _scroll_button_held = NULL;
      advance_page();
      _next_advance_time = 
        ClockObject::get_global_clock()->get_frame_time() + scroll_initial_delay;
    }
  }
  PGItem::press(param, background);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::release
//       Access: Public, Virtual
//  Description: This is a callback hook function, called whenever a
//               mouse or keyboard button previously depressed with
//               press() is released.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
release(const MouseWatcherParameter &param, bool background) {
  if (MouseButton::is_mouse_button(param.get_button())) {
    _mouse_button_page = false;
  }
  if (_dragging) {
    end_drag();
  }
  PGItem::release(param, background);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::move
//       Access: Protected, Virtual
//  Description: This is a callback hook function, called whenever a
//               mouse is moved while within the region.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
move(const MouseWatcherParameter &param) {
  _mouse_pos = param.get_mouse();
  if (_dragging) {
    // We only get here if we the user originally clicked on the
    // track, which caused the slider to move all the way to the mouse
    // position, and then started dragging the mouse along the track.
    // In this case, we start moving the thumb as if the user had
    // started by dragging the thumb directly.
    continue_drag();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::has_cull_callback
//       Access: Protected, Virtual
//  Description: Should be overridden by derived classes to return
//               true if cull_callback() has been defined.  Otherwise,
//               returns false to indicate cull_callback() does not
//               need to be called for this node during the cull
//               traversal.
////////////////////////////////////////////////////////////////////
bool PGSliderBar::
has_cull_callback() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::cull_callback
//       Access: Protected, Virtual
//  Description: If has_cull_callback() returns true, this function
//               will be called during the cull traversal to perform
//               any additional operations that should be performed at
//               cull time.  This may include additional manipulation
//               of render state or additional visible/invisible
//               decisions, or any other arbitrary operation.
//
//               By the time this function is called, the node has
//               already passed the bounding-volume test for the
//               viewing frustum, and the node's transform and state
//               have already been applied to the indicated
//               CullTraverserData object.
//
//               The return value is true if this node should be
//               visible, or false if it should be culled.
////////////////////////////////////////////////////////////////////
bool PGSliderBar::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
  if (_manage_pieces && _needs_remanage) {
    remanage();
  }
  if (_needs_recompute) {
    recompute();
  }

  if (_scroll_button_held != (PGItem *)NULL && 
      _next_advance_time <= ClockObject::get_global_clock()->get_frame_time()) {
    advance_scroll();
  }

  if (_mouse_button_page && 
      _next_advance_time <= ClockObject::get_global_clock()->get_frame_time()) {
    advance_page();
  }

  if (_needs_reposition) {
    reposition();
  }

  return PGItem::cull_callback(trav, data);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::xform
//       Access: Public, Virtual
//  Description: Transforms the contents of this node by the indicated
//               matrix, if it means anything to do so.  For most
//               kinds of nodes, this does nothing.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
xform(const LMatrix4f &mat) {
  PGItem::xform(mat);
  _axis = _axis * mat;

  // Make sure we set the thumb to identity position first, so it
  // won't be accidentally flattened.
  if (_thumb_button != (PGButton *)NULL) {
    _thumb_button->clear_transform();
  }

  _needs_remanage = true;
  _needs_recompute = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::adjust
//       Access: Public, Virtual
//  Description: This is a callback hook function, called whenever the
//               slider value is adjusted by the user or
//               programmatically.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
adjust() {
  string event = get_adjust_event();
  play_sound(event);
  throw_event(event);

  if (has_notify()) {
    get_notify()->slider_bar_adjust(this);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::setup_scroll_bar
//       Access: Published
//  Description: Creates PGSliderBar that represents a vertical or
//               horizontal scroll bar (if vertical is true or false,
//               respectively), with additional buttons for scrolling,
//               and a range of 0 .. 1.
//
//               length here is the measurement along the scroll bar,
//               and width is the measurement across the scroll bar,
//               whether it is vertical or horizontal (so for a
//               horizontal scroll bar, the length is actually the x
//               dimension, and the width is the y dimension).
////////////////////////////////////////////////////////////////////
void PGSliderBar::
setup_scroll_bar(bool vertical, float length, float width, float bevel) {
  set_state(0);
  clear_state_def(0);

  if (vertical) {
    set_frame(-width / 2.0f, width / 2.0f, -length / 2.0f, length / 2.0f);
    _axis = LVector3f::rfu(0.0f, 0.0f, -1.0f);
  } else {
    set_frame(-length / 2.0f, length / 2.0f, -width / 2.0f, width / 2.0f);
    _axis = LVector3f::rfu(1.0f, 0.0f, 0.0f);
  }

  PGFrameStyle style;
  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
  style.set_type(PGFrameStyle::T_flat);
  set_frame_style(0, style);

  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
  style.set_type(PGFrameStyle::T_bevel_out);
  style.set_width(bevel, bevel);

  // Remove the button nodes created by a previous call to setup(), if
  // any.
  if (_thumb_button != (PGButton *)NULL) {
    remove_child(_thumb_button);
    set_thumb_button(NULL);
  }
  if (_left_button != (PGButton *)NULL) {
    remove_child(_left_button);
    set_left_button(NULL);
  }
  if (_right_button != (PGButton *)NULL) {
    remove_child(_right_button);
    set_right_button(NULL);
  }

  PT(PGButton) thumb = new PGButton("thumb");
  thumb->setup("", bevel);
  thumb->set_frame(-width / 2.0f, width / 2.0f, 
                   -width / 2.0f, width / 2.0f);
  add_child(thumb);
  set_thumb_button(thumb);

  PT(PGButton) left = new PGButton("left");
  left->setup("", bevel);
  left->set_frame(-width / 2.0f, width / 2.0f, 
                  -width / 2.0f, width / 2.0f);
  left->set_transform(TransformState::make_pos(((width - length) / 2.0f) * _axis));
  add_child(left);
  set_left_button(left);

  PT(PGButton) right = new PGButton("right");
  right->setup("", bevel);
  right->set_frame(-width / 2.0f, width / 2.0f, 
                   -width / 2.0f, width / 2.0f);
  right->set_transform(TransformState::make_pos(((length - width) / 2.0f) * _axis));
  add_child(right);
  set_right_button(right);

  set_resize_thumb(true);
  set_manage_pieces(true);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::setup_slider
//       Access: Published
//  Description: Creates PGSliderBar that represents a slider that the
//               user can use to control an analog quantity.
//
//               This is functionally the same as a scroll bar, but it
//               has a distinctive look.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
setup_slider(bool vertical, float length, float width, float bevel) {
  set_state(0);
  clear_state_def(0);

  if (vertical) {
    set_frame(-width / 2.0f, width / 2.0f, -length / 2.0f, length / 2.0f);
    _axis = LVector3f::rfu(0.0f, 0.0f, -1.0f);
  } else {
    set_frame(-length / 2.0f, length / 2.0f, -width / 2.0f, width / 2.0f);
    _axis = LVector3f::rfu(1.0f, 0.0f, 0.0f);
  }

  PGFrameStyle style;
  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
  style.set_type(PGFrameStyle::T_flat);
  style.set_visible_scale(1.0f, 0.25f);
  style.set_width(bevel, bevel);
  set_frame_style(0, style);

  // Remove the button nodes created by a previous call to setup(), if
  // any.
  if (_thumb_button != (PGButton *)NULL) {
    remove_child(_thumb_button);
    set_thumb_button(NULL);
  }
  if (_left_button != (PGButton *)NULL) {
    remove_child(_left_button);
    set_left_button(NULL);
  }
  if (_right_button != (PGButton *)NULL) {
    remove_child(_right_button);
    set_right_button(NULL);
  }

  PT(PGButton) thumb = new PGButton("thumb");
  thumb->setup(" ", bevel);
  thumb->set_frame(-width / 4.0f, width / 4.0f, 
                   -width / 2.0f, width / 2.0f);
  add_child(thumb);
  set_thumb_button(thumb);

  set_resize_thumb(false);
  set_manage_pieces(true);
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::set_active
//       Access: Published, Virtual
//  Description: Sets whether the PGItem is active for mouse watching.
//               This is not necessarily related to the
//               active/inactive appearance of the item, which is
//               controlled by set_state(), but it does affect whether
//               it responds to mouse events.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
set_active(bool active) {
  PGItem::set_active(active);

  // This also implicitly sets the managed pieces.
  if (_thumb_button != (PGButton *)NULL) {
    _thumb_button->set_active(active);
  }
  if (_left_button != (PGButton *)NULL) {
    _left_button->set_active(active);
  }
  if (_right_button != (PGButton *)NULL) {
    _right_button->set_active(active);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::remanage
//       Access: Published
//  Description: Manages the position and size of the scroll bars and
//               the thumb.  Normally this should not need to be
//               called directly.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
remanage() {
  _needs_remanage = false;

  const LVecBase4f &frame = get_frame();

  float width, length;
  if (fabs(_axis[0]) > fabs(_axis[1] + _axis[2])) {
    // The slider is X-dominant.
    width = frame[3] - frame[2];
    length = frame[1] - frame[0];
    
  } else {
    // The slider is Y-dominant.
    width = frame[1] - frame[0];
    length = frame[3] - frame[2];
  }

  LVector3f center = LVector3f::rfu((frame[0] + frame[1]) / 2.0f,
                                    0.0f,
                                    (frame[2] + frame[3]) / 2.0f);

  if (_left_button != (PGButton *)NULL) {
    _left_button->set_frame(-width / 2.0f, width / 2.0f, 
                            -width / 2.0f, width / 2.0f);
    _left_button->xform(LMatrix4f::translate_mat(center + ((width - length) / 2.0f) * _axis));
    _left_button->clear_transform();
  }

  if (_right_button != (PGButton *)NULL) {
    _right_button->set_frame(-width / 2.0f, width / 2.0f, 
                             -width / 2.0f, width / 2.0f);
    _right_button->xform(LMatrix4f::translate_mat(center + ((length - width) / 2.0f) * _axis));
    _right_button->clear_transform();
  }

  if (_thumb_button != (PGButton *)NULL) {
    _thumb_button->set_frame(-width / 2.0f, width / 2.0f, 
                             -width / 2.0f, width / 2.0f);
    _thumb_button->set_transform(TransformState::make_pos(center));
  }

  recompute();
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::recompute
//       Access: Published
//  Description: Recomputes the position and size of the thumb.
//               Normally this should not need to be called directly.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
recompute() {
  _needs_recompute = false;

  if (_min_value != _max_value) {
    _scroll_ratio = fabs(_scroll_value / (_max_value - _min_value));
    _page_ratio = fabs(_page_value / (_max_value - _min_value));

  } else {
    _scroll_ratio = 0.0f;
    _page_ratio = 0.0f;
  }

  LVecBase4f frame = get_frame();
  reduce_region(frame, _left_button);
  reduce_region(frame, _right_button);

  if (fabs(_axis[0]) > fabs(_axis[1] + _axis[2])) {
    // The slider is X-dominant.
    
    _min_x = frame[0];
    _max_x = frame[1];
  
    float trough_width = _max_x - _min_x;
    
    if (_thumb_button != (PGButton *)NULL) {
      const LVecBase4f &thumb_frame = _thumb_button->get_frame();

      if (_resize_thumb) {
        // If we're allowed to adjust the thumb's size, we don't need to
        // find out how wide it is.
        _thumb_width = trough_width * min(1.0f, _page_ratio);
        _thumb_button->set_frame(-_thumb_width / 2.0f, _thumb_width / 2.0f,
                                 thumb_frame[2], thumb_frame[3]);
      } else {
        // If we're not adjusting the thumb's size, we do need to know
        // its current width.
        _thumb_width = thumb_frame[1] - thumb_frame[0];
      }
    }

    _range_x = trough_width - _thumb_width;

    const LVecBase4f &thumb_frame = _thumb_button->get_frame();
    if (_axis[0] >= 0.0f) {
      // The slider runs forwards, left to right.
      _thumb_start = (_min_x - thumb_frame[0]) * _axis;
    } else {
      // The slider runs backwards: right to left.
      _thumb_start = (thumb_frame[1] - _max_x) * _axis;
    }
    _thumb_start += LVector3f::rfu(0.0f, 0.0f, (frame[2] + frame[3]) / 2.0f);

  } else {
    // The slider is Y-dominant.  We call it X in the variable names,
    // but it's really Y (or even Z).
    
    _min_x = frame[2];
    _max_x = frame[3];
  
    float trough_width = _max_x - _min_x;
    
    if (_thumb_button == (PGButton *)NULL) {
      _thumb_width = 0.0f;
      _range_x = 0.0f;
      _thumb_start.set(0.0f, 0.0f, 0.0f);

    } else {
      const LVecBase4f &thumb_frame = _thumb_button->get_frame();

      if (_resize_thumb) {
        // If we're allowed to adjust the thumb's size, we don't need to
        // find out how wide it is.
        _thumb_width = trough_width * min(1.0f, _page_ratio);
        _thumb_button->set_frame(thumb_frame[0], thumb_frame[1],
                                 -_thumb_width / 2.0f, _thumb_width / 2.0f);
      } else {
        // If we're not adjusting the thumb's size, we do need to know
        // its current width.
        _thumb_width = thumb_frame[3] - thumb_frame[2];
      }

      _range_x = trough_width - _thumb_width;

      if (_axis[1] >= 0.0f && _axis[2] >= 0.0f) {
        // The slider runs forwards, bottom to top.
        _thumb_start = (_min_x - thumb_frame[2]) * _axis;
      } else {
        // The slider runs backwards: top to bottom.
        _thumb_start = (thumb_frame[3] - _max_x) * _axis;
      }
      _thumb_start += LVector3f::rfu((frame[0] + frame[1]) / 2.0f, 0.0f, 0.0f);
    }
  }
    
  reposition();
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::frame_changed
//       Access: Protected, Virtual
//  Description: Called when the user changes the frame size.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
frame_changed() {
  PGItem::frame_changed();
  _needs_remanage = true;
  _needs_recompute = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_transform_changed
//       Access: Protected, Virtual
//  Description: Called whenever a watched PGItem's local transform
//               has been changed.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_transform_changed(PGItem *) {
  _needs_recompute = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_frame_changed
//       Access: Protected, Virtual
//  Description: Called whenever a watched PGItem's frame
//               has been changed.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_frame_changed(PGItem *) {
  _needs_recompute = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_draw_mask_changed
//       Access: Protected, Virtual
//  Description: Called whenever a watched PGItem's draw_mask
//               has been changed.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_draw_mask_changed(PGItem *) {
  _needs_recompute = true;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_press
//       Access: Protected, Virtual
//  Description: Called whenever the "press" event is triggered on a
//               watched PGItem.  See PGItem::press().
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_press(PGItem *item, const MouseWatcherParameter &param) {
  if (param.has_mouse()) {
    _mouse_pos = param.get_mouse();
  }
  if (item == _left_button || item == _right_button) {
    _scroll_button_held = item;
    _mouse_button_page = false;
    advance_scroll();
    _next_advance_time = 
      ClockObject::get_global_clock()->get_frame_time() + scroll_initial_delay;

  } else if (item == _thumb_button) {
    _scroll_button_held = NULL;
    begin_drag();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_release
//       Access: Protected, Virtual
//  Description: Called whenever the "release" event is triggered on a
//               watched PGItem.  See PGItem::release().
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_release(PGItem *item, const MouseWatcherParameter &) {
  if (item == _scroll_button_held) {
    _scroll_button_held = NULL;

  } else if (item == _thumb_button) {
    _scroll_button_held = NULL;
    if (_dragging) {
      end_drag();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::item_move
//       Access: Protected, Virtual
//  Description: Called whenever the "move" event is triggered on a
//               watched PGItem.  See PGItem::move().
////////////////////////////////////////////////////////////////////
void PGSliderBar::
item_move(PGItem *item, const MouseWatcherParameter &param) {
  _mouse_pos = param.get_mouse();
  if (item == _thumb_button) {
    if (_dragging) {
      continue_drag();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::reposition
//       Access: Private
//  Description: A lighter-weight version of recompute(), this just
//               moves the thumb, assuming all other properties are
//               unchanged.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
reposition() {
  _needs_reposition = false;

  float t = get_ratio();
  
  if (_thumb_button != (PGButton *)NULL) {
    _thumb_button->set_transform(TransformState::make_pos((t * _range_x) * _axis + _thumb_start));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::advance_scroll
//       Access: Private
//  Description: Advances the scroll bar by one unit in the left or
//               right direction while the user is holding down the
//               left or right scroll button.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
advance_scroll() {
  if (_scroll_button_held == _left_button) {
    internal_set_ratio(max(_ratio - _scroll_ratio, 0.0f));

  } else if (_scroll_button_held == _right_button) {
    internal_set_ratio(min(_ratio + _scroll_ratio, 1.0f));
  }

  _next_advance_time = 
    ClockObject::get_global_clock()->get_frame_time() + scroll_continued_delay;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::advance_page
//       Access: Private
//  Description: Advances the scroll bar by one page in the left or
//               right direction while the user is holding down the
//               mouse button on the track.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
advance_page() {
  // Is the mouse position left or right of the current thumb
  // position?
  LPoint3f mouse = mouse_to_local(_mouse_pos) - _thumb_start;
  float target_ratio = mouse.dot(_axis) / _range_x;

  float t;
  if (target_ratio < _ratio) {
    t = max(_ratio - _page_ratio + _scroll_ratio, target_ratio);

  } else {
    t = min(_ratio + _page_ratio - _scroll_ratio, target_ratio);
  }
  internal_set_ratio(t);
  if (t == target_ratio) {
    // We made it; begin dragging from now on until the user releases
    // the mouse.
    begin_drag();
  }

  _next_advance_time = 
    ClockObject::get_global_clock()->get_frame_time() + scroll_continued_delay;
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::begin_drag
//       Access: Private
//  Description: Called when the user clicks down on the thumb button,
//               possibly to begin dragging.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
begin_drag() {
  if (_needs_recompute) {
    recompute();
  }
  if (_range_x != 0.0f) {
    float current_x = mouse_to_local(_mouse_pos).dot(_axis);
    _drag_start_x = current_x - get_ratio() * _range_x;
    _dragging = true;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::continue_drag
//       Access: Private
//  Description: Called as the user moves the mouse while still
//               dragging on the thumb button.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
continue_drag() {
  if (_needs_recompute) {
    recompute();
  }
  if (_range_x != 0.0f) {
    float current_x = mouse_to_local(_mouse_pos).dot(_axis);
    internal_set_ratio((current_x - _drag_start_x) / _range_x);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PGSliderBar::end_drag
//       Access: Private
//  Description: Called as the user releases the mouse after dragging.
////////////////////////////////////////////////////////////////////
void PGSliderBar::
end_drag() {
  _dragging = false;
}
