333 lines
9.7 KiB
C++
333 lines
9.7 KiB
C++
#include "externals.hh"
|
|
#include "c-camera.hh"
|
|
#include "c-utilities.hh"
|
|
#include "ui-utilities.hh"
|
|
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include <imgui_internal.h>
|
|
|
|
|
|
void ImGui::PushDisabled( ) noexcept
|
|
{
|
|
PushItemFlag( ImGuiItemFlags_Disabled , true );
|
|
PushStyleVar( ImGuiStyleVar_Alpha , GetStyle( ).Alpha * .5f );
|
|
}
|
|
|
|
void ImGui::PopDisabled( ) noexcept
|
|
{
|
|
PopItemFlag( );
|
|
PopStyleVar( );
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
bool ImGui::MenuItemCheckbox(
|
|
char const* name ,
|
|
bool* checked ) noexcept
|
|
{
|
|
bool rv{ MenuItem( name , "" , *checked , true ) };
|
|
if ( rv ) {
|
|
*checked = !*checked;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
bool ImGui::ToolbarButton(
|
|
char const* const string ,
|
|
ImVec2 const& size ,
|
|
char const* const tooltip ,
|
|
bool enabled ,
|
|
bool toggled ) noexcept
|
|
{
|
|
if ( !enabled ) {
|
|
PushDisabled( );
|
|
}
|
|
|
|
if ( toggled ) {
|
|
PushStyleColor( ImGuiCol_Border , 0x7fffffff );
|
|
PushStyleColor( ImGuiCol_BorderShadow , 0 );
|
|
PushStyleVar( ImGuiStyleVar_FrameBorderSize , 1 );
|
|
}
|
|
const bool rv{ Button( string , size ) };
|
|
if ( toggled ) {
|
|
PopStyleVar( );
|
|
PopStyleColor( 2 );
|
|
}
|
|
|
|
if ( !enabled ) {
|
|
PopDisabled( );
|
|
} else if ( tooltip && IsItemHovered( ) ) {
|
|
BeginTooltip( );
|
|
Text( tooltip );
|
|
EndTooltip( );
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
void ImGui::ToolbarSeparator( ) noexcept
|
|
{
|
|
SameLine( );
|
|
VerticalSeparator( );
|
|
SameLine( );
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
uint32_t ImGui::ColorHSVAToU32(
|
|
float hue ,
|
|
float saturation ,
|
|
float value ,
|
|
float alpha ) noexcept
|
|
{
|
|
ImVec4 out{ 0, 0, 0, alpha };
|
|
ColorConvertHSVtoRGB( hue , saturation , value ,
|
|
out.x , out.y , out.z );
|
|
return GetColorU32( out );
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------*/
|
|
|
|
// Code stolen and modified from ImGui itself
|
|
bool ImGui::UserScrollbar(
|
|
const bool horizontal ,
|
|
const float total ,
|
|
const float display ,
|
|
float* const scrollPos ,
|
|
const ImVec2 topLeft ,
|
|
const float length ,
|
|
char const* const name ) noexcept
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* const window = g.CurrentWindow;
|
|
ImDrawList* const dl = window->DrawList;
|
|
|
|
const ImGuiStyle& style = g.Style;
|
|
const ImGuiID id = window->GetID( name ? name :
|
|
( horizontal ? "#UserScrollX" : "#UserScrollY" ) );
|
|
|
|
// Render background
|
|
ImRect bb = horizontal
|
|
? ImRect( topLeft , topLeft + ImVec2( length , style.ScrollbarSize ) )
|
|
: ImRect( topLeft , topLeft + ImVec2( style.ScrollbarSize , length ) );
|
|
ImRect contentArea = window->ContentsRegionRect;
|
|
contentArea.Translate( window->Pos );
|
|
if ( !bb.Overlaps( contentArea ) ) {
|
|
return false;
|
|
}
|
|
dl->AddRectFilled( bb.Min , bb.Max , GetColorU32( ImGuiCol_ScrollbarBg ) );
|
|
bb.Expand( ImVec2(
|
|
-ImClamp( (float)(int)( ( bb.Max.x - bb.Min.x - 2.0f ) * 0.5f ) , 0.0f , 3.0f ) ,
|
|
-ImClamp( (float)(int)( ( bb.Max.y - bb.Min.y - 2.0f ) * 0.5f ) , 0.0f , 3.0f ) ) );
|
|
|
|
// V denote the main, longer axis of the scrollbar (= height for a
|
|
// vertical scrollbar)
|
|
const float scrollbarSizeV = horizontal ? bb.GetWidth( ) : bb.GetHeight( );
|
|
float scrollV = *scrollPos;
|
|
|
|
// Calculate the height of our grabbable box. It generally represent
|
|
// the amount visible (vs the total scrollable amount) But we maintain
|
|
// a minimum size in pixel to allow for the user to still aim inside.
|
|
const float maxSize = ImMax( ImMax( total , display ) , 1.0f );
|
|
const float grabSizePx = ImClamp( scrollbarSizeV * ( display / maxSize ) ,
|
|
style.GrabMinSize , scrollbarSizeV );
|
|
const float grabSizeNorm = grabSizePx / scrollbarSizeV;
|
|
|
|
// Handle input right away.
|
|
bool held = false;
|
|
bool hovered = false;
|
|
const bool previouslyHeld = ( g.ActiveId == id );
|
|
ButtonBehavior( bb , id , &hovered , &held );
|
|
|
|
const float scrollMax = ImMax( 1.0f , total - display );
|
|
float scrollRatio = ImSaturate( scrollV / scrollMax );
|
|
float grabNorm = scrollRatio * ( scrollbarSizeV - grabSizePx ) / scrollbarSizeV;
|
|
if ( held && grabSizeNorm < 1.0f ) {
|
|
ImGuiStorage* const storage = window->DC.StateStorage;
|
|
const float scrollbarPosV = horizontal ? bb.Min.x : bb.Min.y;
|
|
const float mousePos = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
|
|
float clickDeltaToGrabCenter = storage->GetFloat( id );
|
|
|
|
// Click position in scrollbar normalized space (0.0f->1.0f)
|
|
const float clickedVNorm = ImSaturate(
|
|
( mousePos - scrollbarPosV ) / scrollbarSizeV );
|
|
SetHoveredID( id );
|
|
|
|
bool seekAbsolute = false;
|
|
if ( !previouslyHeld ) {
|
|
// On initial click calculate the distance between mouse
|
|
// and the center of the grab
|
|
if ( clickedVNorm >= grabNorm && clickedVNorm <= grabNorm + grabSizeNorm ) {
|
|
clickDeltaToGrabCenter = clickedVNorm - grabNorm - grabSizeNorm * 0.5f;
|
|
} else {
|
|
seekAbsolute = true;
|
|
clickDeltaToGrabCenter = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Apply scroll
|
|
const float scrollVNorm = ImSaturate(
|
|
( clickedVNorm - clickDeltaToGrabCenter - grabSizeNorm * 0.5f )
|
|
/ ( 1.0f - grabSizeNorm ) );
|
|
scrollV = (float)(int)( 0.5f + scrollVNorm * scrollMax );
|
|
|
|
// Update values for rendering
|
|
scrollRatio = ImSaturate( scrollV / scrollMax );
|
|
grabNorm = scrollRatio * ( scrollbarSizeV - grabSizePx ) / scrollbarSizeV;
|
|
|
|
// Update distance to grab now that we have seeked and saturated
|
|
if ( seekAbsolute ) {
|
|
clickDeltaToGrabCenter = clickedVNorm - grabNorm - grabSizeNorm * 0.5f;
|
|
}
|
|
storage->SetFloat( id , clickDeltaToGrabCenter );
|
|
}
|
|
|
|
// Render
|
|
const ImU32 grabCol = GetColorU32(
|
|
held ? ImGuiCol_ScrollbarGrabActive
|
|
: hovered ? ImGuiCol_ScrollbarGrabHovered
|
|
: ImGuiCol_ScrollbarGrab);
|
|
ImRect grabRect;
|
|
if ( horizontal ) {
|
|
grabRect = ImRect(
|
|
ImLerp( bb.Min.x , bb.Max.x , grabNorm ) , bb.Min.y,
|
|
ImMin( ImLerp( bb.Min.x , bb.Max.x , grabNorm ) + grabSizePx ,
|
|
contentArea.Max.x ) , bb.Max.y );
|
|
} else {
|
|
grabRect = ImRect(
|
|
bb.Min.x, ImLerp( bb.Min.y , bb.Max.y , grabNorm ) ,
|
|
bb.Max.x , ImMin(
|
|
ImLerp( bb.Min.y , bb.Max.y , grabNorm ) + grabSizePx ,
|
|
contentArea.Max.y ) );
|
|
}
|
|
dl->AddRectFilled( grabRect.Min , grabRect.Max , grabCol , style.ScrollbarRounding );
|
|
|
|
if ( scrollV != *scrollPos ) {
|
|
*scrollPos = scrollV;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*= T_CameraMouseControl =======================================================*/
|
|
|
|
void T_CameraMouseControl::handleDragAndDrop(
|
|
ImVec2 const& move ,
|
|
T_KbdMods modifiers ,
|
|
T_MouseButtons buttons ) noexcept
|
|
{
|
|
if ( move.x == 0 || move.y == 0 ) {
|
|
return;
|
|
}
|
|
|
|
const float fdx( move.x * .1f * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f ) );
|
|
const float fdy( move.y * .1f * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f ) );
|
|
|
|
if ( ( buttons & E_MouseButton::LEFT ) && ( modifiers & E_KbdMod::SHIFT ) ) {
|
|
// Left mouse button, shift - move camera
|
|
const auto side( normalize( cross( camera.upVector( ) , camera.direction( ) ) ) );
|
|
camera.lookAt( ) += .1f * ( side * fdx + camera.upVector( ) * fdy );
|
|
} else if ( buttons & E_MouseButton::LEFT ) {
|
|
// Left mouse button, no shift - change yaw/pitch
|
|
updateAngle( camera.angles( ).y , fdx );
|
|
updateAngle( camera.angles( ).x , fdy );
|
|
} else if ( buttons & E_MouseButton::RIGHT ) {
|
|
// Right mouse button - change roll
|
|
updateAngle( camera.angles( ).z , fdx );
|
|
}
|
|
camera.cvtAnglesToVectors( );
|
|
}
|
|
|
|
void T_CameraMouseControl::handleWheel(
|
|
const float wheel ,
|
|
T_KbdMods modifiers ,
|
|
T_MouseButtons /* buttons */ ) noexcept
|
|
{
|
|
const float delta( wheel * ( ( modifiers & E_KbdMod::CTRL ) ? 1.f : .1f) );
|
|
if ( modifiers & E_KbdMod::SHIFT ) {
|
|
camera.fieldOfView( ) = ImClamp( camera.fieldOfView( ) + delta , 1.f , 179.f );
|
|
camera.cvtFov2Np( );
|
|
} else {
|
|
camera.distance( ) = std::max( .01f , camera.distance( ) - delta );
|
|
camera.cvtAnglesToVectors( );
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
T_CameraChanges CameraUI( T_Camera& camera ) noexcept
|
|
{
|
|
using namespace ImGui;
|
|
|
|
// What gets changed
|
|
static const T_CameraChanges changeFlags[] = {
|
|
E_CameraChange::MATRIX ,
|
|
{} ,
|
|
E_CameraChange::MATRIX ,
|
|
E_CameraChange::MATRIX ,
|
|
{} ,
|
|
E_CameraChange::MATRIX ,
|
|
E_CameraChange::MATRIX ,
|
|
{} ,
|
|
E_CameraChange::FOV ,
|
|
E_CameraChange::FOV ,
|
|
};
|
|
|
|
// Which update gets called.
|
|
enum E_ChangeCall_ {
|
|
CC_NONE ,
|
|
CC_FROM_VECTORS ,
|
|
CC_FROM_ANGLES ,
|
|
CC_FROM_FOV ,
|
|
CC_FROM_NP ,
|
|
};
|
|
static const E_ChangeCall_ changeCalls[] = {
|
|
CC_FROM_ANGLES ,
|
|
CC_NONE ,
|
|
CC_FROM_ANGLES ,
|
|
CC_FROM_ANGLES ,
|
|
CC_NONE ,
|
|
CC_FROM_VECTORS ,
|
|
CC_FROM_VECTORS ,
|
|
CC_NONE ,
|
|
CC_FROM_FOV ,
|
|
CC_FROM_NP ,
|
|
};
|
|
|
|
static_assert( IM_ARRAYSIZE( changeFlags ) == IM_ARRAYSIZE( changeCalls ) ,
|
|
"invalid configuration" );
|
|
|
|
// Changes (also draws the fields)
|
|
const bool changed[] = {
|
|
DragFloat3( "Target" , &camera.lookAt( ).x ) ,
|
|
( Separator( ) , false ) ,
|
|
DragFloat( "Distance" , &camera.distance( ) , .1f , .1f , 1e8 , "%.1f" ) ,
|
|
DragFloat3( "Angles" , &camera.angles( ).x , .01f , -180 , 180 ) ,
|
|
( Separator( ) , false ) ,
|
|
DragFloat3( "Position" , &camera.position( ).x ) ,
|
|
DragFloat3( "Up vector" , &camera.upVector( ).x ) ,
|
|
( Separator( ) , false ) ,
|
|
DragFloat( "FoV" , &camera.fieldOfView( ) , .01f , .01f , 179.9f ) ,
|
|
DragFloat( "Near plane" , &camera.nearPlane( ) , .00001f , .001f , 1e6f ) ,
|
|
};
|
|
|
|
static_assert( IM_ARRAYSIZE( changeFlags ) == IM_ARRAYSIZE( changed ) ,
|
|
"invalid configuration" );
|
|
|
|
T_CameraChanges changes;
|
|
for ( unsigned i = 0 ; i < IM_ARRAYSIZE( changed ) ; i ++ ) {
|
|
if ( changed[ i ] ) {
|
|
switch ( changeCalls[ i ] ) {
|
|
case CC_NONE: break;
|
|
case CC_FROM_VECTORS: camera.cvtVectorsToAngles( ); break;
|
|
case CC_FROM_ANGLES: camera.cvtAnglesToVectors( ); break;
|
|
case CC_FROM_FOV: camera.cvtFov2Np( ); break;
|
|
case CC_FROM_NP: camera.cvtNp2Fov( ); break;
|
|
}
|
|
changes = changes | changeFlags[ i ];
|
|
}
|
|
}
|
|
return changes;
|
|
}
|