imgui_widgets.cpp 353 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640
  1. // dear imgui, v1.75 WIP
  2. // (widgets code)
  3. /*
  4. Index of this file:
  5. // [SECTION] Forward Declarations
  6. // [SECTION] Widgets: Text, etc.
  7. // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
  8. // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
  9. // [SECTION] Widgets: ComboBox
  10. // [SECTION] Data Type and Data Formatting Helpers
  11. // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
  12. // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
  13. // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
  14. // [SECTION] Widgets: InputText, InputTextMultiline
  15. // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
  16. // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
  17. // [SECTION] Widgets: Selectable
  18. // [SECTION] Widgets: ListBox
  19. // [SECTION] Widgets: PlotLines, PlotHistogram
  20. // [SECTION] Widgets: Value helpers
  21. // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
  22. // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
  23. // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
  24. // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
  25. */
  26. #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
  27. #define _CRT_SECURE_NO_WARNINGS
  28. #endif
  29. #include "imgui.h"
  30. #ifndef IMGUI_DEFINE_MATH_OPERATORS
  31. #define IMGUI_DEFINE_MATH_OPERATORS
  32. #endif
  33. #include "imgui_internal.h"
  34. #include <ctype.h> // toupper
  35. #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
  36. #include <stddef.h> // intptr_t
  37. #else
  38. #include <stdint.h> // intptr_t
  39. #endif
  40. // Visual Studio warnings
  41. #ifdef _MSC_VER
  42. #pragma warning (disable: 4127) // condition expression is constant
  43. #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
  44. #endif
  45. // Clang/GCC warnings with -Weverything
  46. #if defined(__clang__)
  47. #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
  48. #pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
  49. #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
  50. #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
  51. #if __has_warning("-Wzero-as-null-pointer-constant")
  52. #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning : zero as null pointer constant // some standard header variations use #define NULL 0
  53. #endif
  54. #if __has_warning("-Wdouble-promotion")
  55. #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
  56. #endif
  57. #elif defined(__GNUC__)
  58. #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
  59. #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
  60. #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
  61. #endif
  62. //-------------------------------------------------------------------------
  63. // Data
  64. //-------------------------------------------------------------------------
  65. // Those MIN/MAX values are not define because we need to point to them
  66. static const signed char IM_S8_MIN = -128;
  67. static const signed char IM_S8_MAX = 127;
  68. static const unsigned char IM_U8_MIN = 0;
  69. static const unsigned char IM_U8_MAX = 0xFF;
  70. static const signed short IM_S16_MIN = -32768;
  71. static const signed short IM_S16_MAX = 32767;
  72. static const unsigned short IM_U16_MIN = 0;
  73. static const unsigned short IM_U16_MAX = 0xFFFF;
  74. static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000);
  75. static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF)
  76. static const ImU32 IM_U32_MIN = 0;
  77. static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF)
  78. #ifdef LLONG_MIN
  79. static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
  80. static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
  81. #else
  82. static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
  83. static const ImS64 IM_S64_MAX = 9223372036854775807LL;
  84. #endif
  85. static const ImU64 IM_U64_MIN = 0;
  86. #ifdef ULLONG_MAX
  87. static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
  88. #else
  89. static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
  90. #endif
  91. //-------------------------------------------------------------------------
  92. // [SECTION] Forward Declarations
  93. //-------------------------------------------------------------------------
  94. // For InputTextEx()
  95. static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
  96. static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
  97. static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
  98. //-------------------------------------------------------------------------
  99. // [SECTION] Widgets: Text, etc.
  100. //-------------------------------------------------------------------------
  101. // - TextEx() [Internal]
  102. // - TextUnformatted()
  103. // - Text()
  104. // - TextV()
  105. // - TextColored()
  106. // - TextColoredV()
  107. // - TextDisabled()
  108. // - TextDisabledV()
  109. // - TextWrapped()
  110. // - TextWrappedV()
  111. // - LabelText()
  112. // - LabelTextV()
  113. // - BulletText()
  114. // - BulletTextV()
  115. //-------------------------------------------------------------------------
  116. void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
  117. {
  118. ImGuiWindow* window = GetCurrentWindow();
  119. if (window->SkipItems)
  120. return;
  121. ImGuiContext& g = *GImGui;
  122. IM_ASSERT(text != NULL);
  123. const char* text_begin = text;
  124. if (text_end == NULL)
  125. text_end = text + strlen(text); // FIXME-OPT
  126. const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
  127. const float wrap_pos_x = window->DC.TextWrapPos;
  128. const bool wrap_enabled = (wrap_pos_x >= 0.0f);
  129. if (text_end - text > 2000 && !wrap_enabled)
  130. {
  131. // Long text!
  132. // Perform manual coarse clipping to optimize for long multi-line text
  133. // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
  134. // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
  135. // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
  136. const char* line = text;
  137. const float line_height = GetTextLineHeight();
  138. ImVec2 text_size(0,0);
  139. // Lines to skip (can't skip when logging text)
  140. ImVec2 pos = text_pos;
  141. if (!g.LogEnabled)
  142. {
  143. int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
  144. if (lines_skippable > 0)
  145. {
  146. int lines_skipped = 0;
  147. while (line < text_end && lines_skipped < lines_skippable)
  148. {
  149. const char* line_end = (const char*)memchr(line, '\n', text_end - line);
  150. if (!line_end)
  151. line_end = text_end;
  152. if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
  153. text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
  154. line = line_end + 1;
  155. lines_skipped++;
  156. }
  157. pos.y += lines_skipped * line_height;
  158. }
  159. }
  160. // Lines to render
  161. if (line < text_end)
  162. {
  163. ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
  164. while (line < text_end)
  165. {
  166. if (IsClippedEx(line_rect, 0, false))
  167. break;
  168. const char* line_end = (const char*)memchr(line, '\n', text_end - line);
  169. if (!line_end)
  170. line_end = text_end;
  171. text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
  172. RenderText(pos, line, line_end, false);
  173. line = line_end + 1;
  174. line_rect.Min.y += line_height;
  175. line_rect.Max.y += line_height;
  176. pos.y += line_height;
  177. }
  178. // Count remaining lines
  179. int lines_skipped = 0;
  180. while (line < text_end)
  181. {
  182. const char* line_end = (const char*)memchr(line, '\n', text_end - line);
  183. if (!line_end)
  184. line_end = text_end;
  185. if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
  186. text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
  187. line = line_end + 1;
  188. lines_skipped++;
  189. }
  190. pos.y += lines_skipped * line_height;
  191. }
  192. text_size.y = (pos - text_pos).y;
  193. ImRect bb(text_pos, text_pos + text_size);
  194. ItemSize(text_size, 0.0f);
  195. ItemAdd(bb, 0);
  196. }
  197. else
  198. {
  199. const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
  200. const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
  201. ImRect bb(text_pos, text_pos + text_size);
  202. ItemSize(text_size, 0.0f);
  203. if (!ItemAdd(bb, 0))
  204. return;
  205. // Render (we don't hide text after ## in this end-user function)
  206. RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
  207. }
  208. }
  209. void ImGui::TextUnformatted(const char* text, const char* text_end)
  210. {
  211. TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
  212. }
  213. void ImGui::Text(const char* fmt, ...)
  214. {
  215. va_list args;
  216. va_start(args, fmt);
  217. TextV(fmt, args);
  218. va_end(args);
  219. }
  220. void ImGui::TextV(const char* fmt, va_list args)
  221. {
  222. ImGuiWindow* window = GetCurrentWindow();
  223. if (window->SkipItems)
  224. return;
  225. ImGuiContext& g = *GImGui;
  226. const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
  227. TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
  228. }
  229. void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
  230. {
  231. va_list args;
  232. va_start(args, fmt);
  233. TextColoredV(col, fmt, args);
  234. va_end(args);
  235. }
  236. void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
  237. {
  238. PushStyleColor(ImGuiCol_Text, col);
  239. TextV(fmt, args);
  240. PopStyleColor();
  241. }
  242. void ImGui::TextDisabled(const char* fmt, ...)
  243. {
  244. va_list args;
  245. va_start(args, fmt);
  246. TextDisabledV(fmt, args);
  247. va_end(args);
  248. }
  249. void ImGui::TextDisabledV(const char* fmt, va_list args)
  250. {
  251. PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
  252. TextV(fmt, args);
  253. PopStyleColor();
  254. }
  255. void ImGui::TextWrapped(const char* fmt, ...)
  256. {
  257. va_list args;
  258. va_start(args, fmt);
  259. TextWrappedV(fmt, args);
  260. va_end(args);
  261. }
  262. void ImGui::TextWrappedV(const char* fmt, va_list args)
  263. {
  264. ImGuiWindow* window = GetCurrentWindow();
  265. bool need_backup = (window->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set
  266. if (need_backup)
  267. PushTextWrapPos(0.0f);
  268. TextV(fmt, args);
  269. if (need_backup)
  270. PopTextWrapPos();
  271. }
  272. void ImGui::LabelText(const char* label, const char* fmt, ...)
  273. {
  274. va_list args;
  275. va_start(args, fmt);
  276. LabelTextV(label, fmt, args);
  277. va_end(args);
  278. }
  279. // Add a label+text combo aligned to other label+value widgets
  280. void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
  281. {
  282. ImGuiWindow* window = GetCurrentWindow();
  283. if (window->SkipItems)
  284. return;
  285. ImGuiContext& g = *GImGui;
  286. const ImGuiStyle& style = g.Style;
  287. const float w = CalcItemWidth();
  288. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  289. const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
  290. const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
  291. ItemSize(total_bb, style.FramePadding.y);
  292. if (!ItemAdd(total_bb, 0))
  293. return;
  294. // Render
  295. const char* value_text_begin = &g.TempBuffer[0];
  296. const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
  297. RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
  298. if (label_size.x > 0.0f)
  299. RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
  300. }
  301. void ImGui::BulletText(const char* fmt, ...)
  302. {
  303. va_list args;
  304. va_start(args, fmt);
  305. BulletTextV(fmt, args);
  306. va_end(args);
  307. }
  308. // Text with a little bullet aligned to the typical tree node.
  309. void ImGui::BulletTextV(const char* fmt, va_list args)
  310. {
  311. ImGuiWindow* window = GetCurrentWindow();
  312. if (window->SkipItems)
  313. return;
  314. ImGuiContext& g = *GImGui;
  315. const ImGuiStyle& style = g.Style;
  316. const char* text_begin = g.TempBuffer;
  317. const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
  318. const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
  319. const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding
  320. ImVec2 pos = window->DC.CursorPos;
  321. pos.y += window->DC.CurrLineTextBaseOffset;
  322. ItemSize(total_size, 0.0f);
  323. const ImRect bb(pos, pos + total_size);
  324. if (!ItemAdd(bb, 0))
  325. return;
  326. // Render
  327. ImU32 text_col = GetColorU32(ImGuiCol_Text);
  328. RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, g.FontSize*0.5f), text_col);
  329. RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
  330. }
  331. //-------------------------------------------------------------------------
  332. // [SECTION] Widgets: Main
  333. //-------------------------------------------------------------------------
  334. // - ButtonBehavior() [Internal]
  335. // - Button()
  336. // - SmallButton()
  337. // - InvisibleButton()
  338. // - ArrowButton()
  339. // - CloseButton() [Internal]
  340. // - CollapseButton() [Internal]
  341. // - ScrollbarEx() [Internal]
  342. // - Scrollbar() [Internal]
  343. // - Image()
  344. // - ImageButton()
  345. // - Checkbox()
  346. // - CheckboxFlags()
  347. // - RadioButton()
  348. // - ProgressBar()
  349. // - Bullet()
  350. //-------------------------------------------------------------------------
  351. // The ButtonBehavior() function is key to many interactions and used by many/most widgets.
  352. // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
  353. // this code is a little complex.
  354. // By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
  355. // See the series of events below and the corresponding state reported by dear imgui:
  356. //------------------------------------------------------------------------------------------------------------------------------------------------
  357. // with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
  358. // Frame N+0 (mouse is outside bb) - - - - - -
  359. // Frame N+1 (mouse moves inside bb) - true - - - -
  360. // Frame N+2 (mouse button is down) - true true true - true
  361. // Frame N+3 (mouse button is down) - true true - - -
  362. // Frame N+4 (mouse moves outside bb) - - true - - -
  363. // Frame N+5 (mouse moves inside bb) - true true - - -
  364. // Frame N+6 (mouse button is released) true true - - true -
  365. // Frame N+7 (mouse button is released) - true - - - -
  366. // Frame N+8 (mouse moves outside bb) - - - - - -
  367. //------------------------------------------------------------------------------------------------------------------------------------------------
  368. // with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
  369. // Frame N+2 (mouse button is down) true true true true - true
  370. // Frame N+3 (mouse button is down) - true true - - -
  371. // Frame N+6 (mouse button is released) - true - - true -
  372. // Frame N+7 (mouse button is released) - true - - - -
  373. //------------------------------------------------------------------------------------------------------------------------------------------------
  374. // with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
  375. // Frame N+2 (mouse button is down) - true - - - true
  376. // Frame N+3 (mouse button is down) - true - - - -
  377. // Frame N+6 (mouse button is released) true true - - - -
  378. // Frame N+7 (mouse button is released) - true - - - -
  379. //------------------------------------------------------------------------------------------------------------------------------------------------
  380. // with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked()
  381. // Frame N+0 (mouse button is down) - true - - - true
  382. // Frame N+1 (mouse button is down) - true - - - -
  383. // Frame N+2 (mouse button is released) - true - - - -
  384. // Frame N+3 (mouse button is released) - true - - - -
  385. // Frame N+4 (mouse button is down) true true true true - true
  386. // Frame N+5 (mouse button is down) - true true - - -
  387. // Frame N+6 (mouse button is released) - true - - true -
  388. // Frame N+7 (mouse button is released) - true - - - -
  389. //------------------------------------------------------------------------------------------------------------------------------------------------
  390. // Note that some combinations are supported,
  391. // - PressedOnDragDropHold can generally be associated with any flag.
  392. // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
  393. //------------------------------------------------------------------------------------------------------------------------------------------------
  394. // The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
  395. // Repeat+ Repeat+ Repeat+ Repeat+
  396. // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick
  397. //-------------------------------------------------------------------------------------------------------------------------------------------------
  398. // Frame N+0 (mouse button is down) - true - true
  399. // ... - - - -
  400. // Frame N + RepeatDelay true true - true
  401. // ... - - - -
  402. // Frame N + RepeatDelay + RepeatRate*N true true - true
  403. //-------------------------------------------------------------------------------------------------------------------------------------------------
  404. bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
  405. {
  406. ImGuiContext& g = *GImGui;
  407. ImGuiWindow* window = GetCurrentWindow();
  408. if (flags & ImGuiButtonFlags_Disabled)
  409. {
  410. if (out_hovered) *out_hovered = false;
  411. if (out_held) *out_held = false;
  412. if (g.ActiveId == id) ClearActiveID();
  413. return false;
  414. }
  415. // Default behavior requires click+release on same spot
  416. if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
  417. flags |= ImGuiButtonFlags_PressedOnClickRelease;
  418. ImGuiWindow* backup_hovered_window = g.HoveredWindow;
  419. const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window;
  420. if (flatten_hovered_children)
  421. g.HoveredWindow = window;
  422. #ifdef IMGUI_ENABLE_TEST_ENGINE
  423. if (id != 0 && window->DC.LastItemId != id)
  424. ImGuiTestEngineHook_ItemAdd(&g, bb, id);
  425. #endif
  426. bool pressed = false;
  427. bool hovered = ItemHoverable(bb, id);
  428. // Drag source doesn't report as hovered
  429. if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
  430. hovered = false;
  431. // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
  432. if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
  433. if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
  434. {
  435. hovered = true;
  436. SetHoveredID(id);
  437. if (CalcTypematicRepeatAmount(g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, g.HoveredIdTimer + 0.0001f, 0.70f, 0.00f))
  438. {
  439. pressed = true;
  440. FocusWindow(window);
  441. }
  442. }
  443. if (flatten_hovered_children)
  444. g.HoveredWindow = backup_hovered_window;
  445. // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
  446. if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
  447. hovered = false;
  448. // Mouse
  449. if (hovered)
  450. {
  451. if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
  452. {
  453. if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
  454. {
  455. SetActiveID(id, window);
  456. if (!(flags & ImGuiButtonFlags_NoNavFocus))
  457. SetFocusID(id, window);
  458. FocusWindow(window);
  459. }
  460. if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
  461. {
  462. pressed = true;
  463. if (flags & ImGuiButtonFlags_NoHoldingActiveID)
  464. ClearActiveID();
  465. else
  466. SetActiveID(id, window); // Hold on ID
  467. FocusWindow(window);
  468. }
  469. if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
  470. {
  471. if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
  472. pressed = true;
  473. ClearActiveID();
  474. }
  475. // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
  476. // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
  477. if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
  478. pressed = true;
  479. }
  480. if (pressed)
  481. g.NavDisableHighlight = true;
  482. }
  483. // Gamepad/Keyboard navigation
  484. // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
  485. if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
  486. if (!(flags & ImGuiButtonFlags_NoHoveredOnNav))
  487. hovered = true;
  488. if (g.NavActivateDownId == id)
  489. {
  490. bool nav_activated_by_code = (g.NavActivateId == id);
  491. bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
  492. if (nav_activated_by_code || nav_activated_by_inputs)
  493. pressed = true;
  494. if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
  495. {
  496. // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
  497. g.NavActivateId = id; // This is so SetActiveId assign a Nav source
  498. SetActiveID(id, window);
  499. if ((nav_activated_by_code || nav_activated_by_inputs) && !(flags & ImGuiButtonFlags_NoNavFocus))
  500. SetFocusID(id, window);
  501. }
  502. }
  503. bool held = false;
  504. if (g.ActiveId == id)
  505. {
  506. if (pressed)
  507. g.ActiveIdHasBeenPressedBefore = true;
  508. if (g.ActiveIdSource == ImGuiInputSource_Mouse)
  509. {
  510. if (g.ActiveIdIsJustActivated)
  511. g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
  512. if (g.IO.MouseDown[0])
  513. {
  514. held = true;
  515. }
  516. else
  517. {
  518. if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) && !g.DragDropActive)
  519. {
  520. bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDownWasDoubleClick[0];
  521. bool is_repeating_already = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
  522. if (!is_double_click_release && !is_repeating_already)
  523. pressed = true;
  524. }
  525. ClearActiveID();
  526. }
  527. if (!(flags & ImGuiButtonFlags_NoNavFocus))
  528. g.NavDisableHighlight = true;
  529. }
  530. else if (g.ActiveIdSource == ImGuiInputSource_Nav)
  531. {
  532. if (g.NavActivateDownId != id)
  533. ClearActiveID();
  534. }
  535. }
  536. if (out_hovered) *out_hovered = hovered;
  537. if (out_held) *out_held = held;
  538. return pressed;
  539. }
  540. bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
  541. {
  542. ImGuiWindow* window = GetCurrentWindow();
  543. if (window->SkipItems)
  544. return false;
  545. ImGuiContext& g = *GImGui;
  546. const ImGuiStyle& style = g.Style;
  547. const ImGuiID id = window->GetID(label);
  548. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  549. ImVec2 pos = window->DC.CursorPos;
  550. if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
  551. pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
  552. ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
  553. const ImRect bb(pos, pos + size);
  554. ItemSize(size, style.FramePadding.y);
  555. if (!ItemAdd(bb, id))
  556. return false;
  557. if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
  558. flags |= ImGuiButtonFlags_Repeat;
  559. bool hovered, held;
  560. bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
  561. // Render
  562. const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  563. RenderNavHighlight(bb, id);
  564. RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
  565. RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
  566. // Automatically close popups
  567. //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
  568. // CloseCurrentPopup();
  569. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
  570. return pressed;
  571. }
  572. bool ImGui::Button(const char* label, const ImVec2& size_arg)
  573. {
  574. return ButtonEx(label, size_arg, 0);
  575. }
  576. // Small buttons fits within text without additional vertical spacing.
  577. bool ImGui::SmallButton(const char* label)
  578. {
  579. ImGuiContext& g = *GImGui;
  580. float backup_padding_y = g.Style.FramePadding.y;
  581. g.Style.FramePadding.y = 0.0f;
  582. bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
  583. g.Style.FramePadding.y = backup_padding_y;
  584. return pressed;
  585. }
  586. // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
  587. // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
  588. bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
  589. {
  590. ImGuiWindow* window = GetCurrentWindow();
  591. if (window->SkipItems)
  592. return false;
  593. // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
  594. IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
  595. const ImGuiID id = window->GetID(str_id);
  596. ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
  597. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
  598. ItemSize(size);
  599. if (!ItemAdd(bb, id))
  600. return false;
  601. bool hovered, held;
  602. bool pressed = ButtonBehavior(bb, id, &hovered, &held);
  603. return pressed;
  604. }
  605. bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
  606. {
  607. ImGuiWindow* window = GetCurrentWindow();
  608. if (window->SkipItems)
  609. return false;
  610. ImGuiContext& g = *GImGui;
  611. const ImGuiID id = window->GetID(str_id);
  612. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
  613. const float default_size = GetFrameHeight();
  614. ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
  615. if (!ItemAdd(bb, id))
  616. return false;
  617. if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
  618. flags |= ImGuiButtonFlags_Repeat;
  619. bool hovered, held;
  620. bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
  621. // Render
  622. const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  623. const ImU32 text_col = GetColorU32(ImGuiCol_Text);
  624. RenderNavHighlight(bb, id);
  625. RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
  626. RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir);
  627. return pressed;
  628. }
  629. bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
  630. {
  631. float sz = GetFrameHeight();
  632. return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
  633. }
  634. // Button to close a window
  635. bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)//, float size)
  636. {
  637. ImGuiContext& g = *GImGui;
  638. ImGuiWindow* window = g.CurrentWindow;
  639. // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
  640. // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
  641. const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
  642. bool is_clipped = !ItemAdd(bb, id);
  643. bool hovered, held;
  644. bool pressed = ButtonBehavior(bb, id, &hovered, &held);
  645. if (is_clipped)
  646. return pressed;
  647. // Render
  648. ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
  649. ImVec2 center = bb.GetCenter();
  650. if (hovered)
  651. window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col, 12);
  652. float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
  653. ImU32 cross_col = GetColorU32(ImGuiCol_Text);
  654. center -= ImVec2(0.5f, 0.5f);
  655. window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
  656. window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
  657. return pressed;
  658. }
  659. bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
  660. {
  661. ImGuiContext& g = *GImGui;
  662. ImGuiWindow* window = g.CurrentWindow;
  663. ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
  664. ItemAdd(bb, id);
  665. bool hovered, held;
  666. bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
  667. // Render
  668. ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  669. ImU32 text_col = GetColorU32(ImGuiCol_Text);
  670. ImVec2 center = bb.GetCenter();
  671. if (hovered || held)
  672. window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
  673. RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
  674. // Switch to moving the window after mouse is moved beyond the initial drag threshold
  675. if (IsItemActive() && IsMouseDragging(0))
  676. StartMouseMovingWindow(window);
  677. return pressed;
  678. }
  679. ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
  680. {
  681. return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
  682. }
  683. // Vertical/Horizontal scrollbar
  684. // The entire piece of code below is rather confusing because:
  685. // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
  686. // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
  687. // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
  688. // Still, the code should probably be made simpler..
  689. bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, float* p_scroll_v, float size_avail_v, float size_contents_v, ImDrawCornerFlags rounding_corners)
  690. {
  691. ImGuiContext& g = *GImGui;
  692. ImGuiWindow* window = g.CurrentWindow;
  693. if (window->SkipItems)
  694. return false;
  695. const float bb_frame_width = bb_frame.GetWidth();
  696. const float bb_frame_height = bb_frame.GetHeight();
  697. if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
  698. return false;
  699. // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab)
  700. float alpha = 1.0f;
  701. if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
  702. alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
  703. if (alpha <= 0.0f)
  704. return false;
  705. const ImGuiStyle& style = g.Style;
  706. const bool allow_interaction = (alpha >= 1.0f);
  707. const bool horizontal = (axis == ImGuiAxis_X);
  708. ImRect bb = bb_frame;
  709. bb.Expand(ImVec2(-ImClamp(IM_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
  710. // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
  711. const float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
  712. // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
  713. // But we maintain a minimum size in pixel to allow for the user to still aim inside.
  714. IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
  715. const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f);
  716. const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
  717. const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
  718. // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
  719. bool held = false;
  720. bool hovered = false;
  721. ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
  722. float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v);
  723. float scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
  724. float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
  725. if (held && allow_interaction && grab_h_norm < 1.0f)
  726. {
  727. float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
  728. float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
  729. // Click position in scrollbar normalized space (0.0f->1.0f)
  730. const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
  731. SetHoveredID(id);
  732. bool seek_absolute = false;
  733. if (g.ActiveIdIsJustActivated)
  734. {
  735. // On initial click calculate the distance between mouse and the center of the grab
  736. seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm);
  737. if (seek_absolute)
  738. g.ScrollbarClickDeltaToGrabCenter = 0.0f;
  739. else
  740. g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
  741. }
  742. // Apply scroll
  743. // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position
  744. const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
  745. *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
  746. // Update values for rendering
  747. scroll_ratio = ImSaturate(*p_scroll_v / scroll_max);
  748. grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
  749. // Update distance to grab now that we have seeked and saturated
  750. if (seek_absolute)
  751. g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
  752. }
  753. // Render
  754. window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, rounding_corners);
  755. const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
  756. ImRect grab_rect;
  757. if (horizontal)
  758. grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);
  759. else
  760. grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels);
  761. window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
  762. return held;
  763. }
  764. void ImGui::Scrollbar(ImGuiAxis axis)
  765. {
  766. ImGuiContext& g = *GImGui;
  767. ImGuiWindow* window = g.CurrentWindow;
  768. const ImGuiID id = GetWindowScrollbarID(window, axis);
  769. KeepAliveID(id);
  770. // Calculate scrollbar bounding box
  771. const ImRect outer_rect = window->Rect();
  772. const ImRect inner_rect = window->InnerRect;
  773. const float border_size = window->WindowBorderSize;
  774. const float scrollbar_size = window->ScrollbarSizes[axis ^ 1];
  775. IM_ASSERT(scrollbar_size > 0.0f);
  776. const float other_scrollbar_size = window->ScrollbarSizes[axis];
  777. ImDrawCornerFlags rounding_corners = (other_scrollbar_size <= 0.0f) ? ImDrawCornerFlags_BotRight : 0;
  778. ImRect bb;
  779. if (axis == ImGuiAxis_X)
  780. {
  781. bb.Min = ImVec2(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size));
  782. bb.Max = ImVec2(inner_rect.Max.x, outer_rect.Max.y);
  783. rounding_corners |= ImDrawCornerFlags_BotLeft;
  784. }
  785. else
  786. {
  787. bb.Min = ImVec2(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y);
  788. bb.Max = ImVec2(outer_rect.Max.x, window->InnerRect.Max.y);
  789. rounding_corners |= ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0;
  790. }
  791. ScrollbarEx(bb, id, axis, &window->Scroll[axis], inner_rect.Max[axis] - inner_rect.Min[axis], window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f, rounding_corners);
  792. }
  793. void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
  794. {
  795. ImGuiWindow* window = GetCurrentWindow();
  796. if (window->SkipItems)
  797. return;
  798. ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
  799. if (border_col.w > 0.0f)
  800. bb.Max += ImVec2(2, 2);
  801. ItemSize(bb);
  802. if (!ItemAdd(bb, 0))
  803. return;
  804. if (border_col.w > 0.0f)
  805. {
  806. window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
  807. window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
  808. }
  809. else
  810. {
  811. window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
  812. }
  813. }
  814. // frame_padding < 0: uses FramePadding from style (default)
  815. // frame_padding = 0: no framing
  816. // frame_padding > 0: set framing size
  817. // The color used are the button colors.
  818. bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
  819. {
  820. ImGuiWindow* window = GetCurrentWindow();
  821. if (window->SkipItems)
  822. return false;
  823. ImGuiContext& g = *GImGui;
  824. const ImGuiStyle& style = g.Style;
  825. // Default to using texture ID as ID. User can still push string/integer prefixes.
  826. // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
  827. PushID((void*)(intptr_t)user_texture_id);
  828. const ImGuiID id = window->GetID("#image");
  829. PopID();
  830. const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
  831. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
  832. const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
  833. ItemSize(bb);
  834. if (!ItemAdd(bb, id))
  835. return false;
  836. bool hovered, held;
  837. bool pressed = ButtonBehavior(bb, id, &hovered, &held);
  838. // Render
  839. const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  840. RenderNavHighlight(bb, id);
  841. RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
  842. if (bg_col.w > 0.0f)
  843. window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
  844. window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
  845. return pressed;
  846. }
  847. bool ImGui::Checkbox(const char* label, bool* v)
  848. {
  849. ImGuiWindow* window = GetCurrentWindow();
  850. if (window->SkipItems)
  851. return false;
  852. ImGuiContext& g = *GImGui;
  853. const ImGuiStyle& style = g.Style;
  854. const ImGuiID id = window->GetID(label);
  855. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  856. const float square_sz = GetFrameHeight();
  857. const ImVec2 pos = window->DC.CursorPos;
  858. const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
  859. ItemSize(total_bb, style.FramePadding.y);
  860. if (!ItemAdd(total_bb, id))
  861. return false;
  862. bool hovered, held;
  863. bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
  864. if (pressed)
  865. {
  866. *v = !(*v);
  867. MarkItemEdited(id);
  868. }
  869. const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
  870. RenderNavHighlight(total_bb, id);
  871. RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
  872. ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
  873. if (window->DC.ItemFlags & ImGuiItemFlags_MixedValue)
  874. {
  875. // Undocumented tristate/mixed/indeterminate checkbox (#2644)
  876. ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)));
  877. window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
  878. }
  879. else if (*v)
  880. {
  881. const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
  882. RenderCheckMark(check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad*2.0f);
  883. }
  884. if (g.LogEnabled)
  885. LogRenderedText(&total_bb.Min, *v ? "[x]" : "[ ]");
  886. if (label_size.x > 0.0f)
  887. RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
  888. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
  889. return pressed;
  890. }
  891. bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
  892. {
  893. bool v = ((*flags & flags_value) == flags_value);
  894. bool pressed = Checkbox(label, &v);
  895. if (pressed)
  896. {
  897. if (v)
  898. *flags |= flags_value;
  899. else
  900. *flags &= ~flags_value;
  901. }
  902. return pressed;
  903. }
  904. bool ImGui::RadioButton(const char* label, bool active)
  905. {
  906. ImGuiWindow* window = GetCurrentWindow();
  907. if (window->SkipItems)
  908. return false;
  909. ImGuiContext& g = *GImGui;
  910. const ImGuiStyle& style = g.Style;
  911. const ImGuiID id = window->GetID(label);
  912. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  913. const float square_sz = GetFrameHeight();
  914. const ImVec2 pos = window->DC.CursorPos;
  915. const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
  916. const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));
  917. ItemSize(total_bb, style.FramePadding.y);
  918. if (!ItemAdd(total_bb, id))
  919. return false;
  920. ImVec2 center = check_bb.GetCenter();
  921. center.x = IM_ROUND(center.x);
  922. center.y = IM_ROUND(center.y);
  923. const float radius = (square_sz - 1.0f) * 0.5f;
  924. bool hovered, held;
  925. bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
  926. if (pressed)
  927. MarkItemEdited(id);
  928. RenderNavHighlight(total_bb, id);
  929. window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
  930. if (active)
  931. {
  932. const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f));
  933. window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
  934. }
  935. if (style.FrameBorderSize > 0.0f)
  936. {
  937. window->DrawList->AddCircle(center + ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
  938. window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
  939. }
  940. if (g.LogEnabled)
  941. LogRenderedText(&total_bb.Min, active ? "(x)" : "( )");
  942. if (label_size.x > 0.0f)
  943. RenderText(ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y), label);
  944. return pressed;
  945. }
  946. // FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..
  947. bool ImGui::RadioButton(const char* label, int* v, int v_button)
  948. {
  949. const bool pressed = RadioButton(label, *v == v_button);
  950. if (pressed)
  951. *v = v_button;
  952. return pressed;
  953. }
  954. // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
  955. void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
  956. {
  957. ImGuiWindow* window = GetCurrentWindow();
  958. if (window->SkipItems)
  959. return;
  960. ImGuiContext& g = *GImGui;
  961. const ImGuiStyle& style = g.Style;
  962. ImVec2 pos = window->DC.CursorPos;
  963. ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f);
  964. ImRect bb(pos, pos + size);
  965. ItemSize(size, style.FramePadding.y);
  966. if (!ItemAdd(bb, 0))
  967. return;
  968. // Render
  969. fraction = ImSaturate(fraction);
  970. RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
  971. bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
  972. const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
  973. RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
  974. // Default displaying the fraction as percentage string, but user can override it
  975. char overlay_buf[32];
  976. if (!overlay)
  977. {
  978. ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
  979. overlay = overlay_buf;
  980. }
  981. ImVec2 overlay_size = CalcTextSize(overlay, NULL);
  982. if (overlay_size.x > 0.0f)
  983. RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
  984. }
  985. void ImGui::Bullet()
  986. {
  987. ImGuiWindow* window = GetCurrentWindow();
  988. if (window->SkipItems)
  989. return;
  990. ImGuiContext& g = *GImGui;
  991. const ImGuiStyle& style = g.Style;
  992. const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
  993. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
  994. ItemSize(bb);
  995. if (!ItemAdd(bb, 0))
  996. {
  997. SameLine(0, style.FramePadding.x*2);
  998. return;
  999. }
  1000. // Render and stay on same line
  1001. ImU32 text_col = GetColorU32(ImGuiCol_Text);
  1002. RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f), text_col);
  1003. SameLine(0, style.FramePadding.x * 2.0f);
  1004. }
  1005. //-------------------------------------------------------------------------
  1006. // [SECTION] Widgets: Low-level Layout helpers
  1007. //-------------------------------------------------------------------------
  1008. // - Spacing()
  1009. // - Dummy()
  1010. // - NewLine()
  1011. // - AlignTextToFramePadding()
  1012. // - SeparatorEx() [Internal]
  1013. // - Separator()
  1014. // - SplitterBehavior() [Internal]
  1015. // - ShrinkWidths() [Internal]
  1016. //-------------------------------------------------------------------------
  1017. void ImGui::Spacing()
  1018. {
  1019. ImGuiWindow* window = GetCurrentWindow();
  1020. if (window->SkipItems)
  1021. return;
  1022. ItemSize(ImVec2(0,0));
  1023. }
  1024. void ImGui::Dummy(const ImVec2& size)
  1025. {
  1026. ImGuiWindow* window = GetCurrentWindow();
  1027. if (window->SkipItems)
  1028. return;
  1029. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
  1030. ItemSize(size);
  1031. ItemAdd(bb, 0);
  1032. }
  1033. void ImGui::NewLine()
  1034. {
  1035. ImGuiWindow* window = GetCurrentWindow();
  1036. if (window->SkipItems)
  1037. return;
  1038. ImGuiContext& g = *GImGui;
  1039. const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
  1040. window->DC.LayoutType = ImGuiLayoutType_Vertical;
  1041. if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
  1042. ItemSize(ImVec2(0,0));
  1043. else
  1044. ItemSize(ImVec2(0.0f, g.FontSize));
  1045. window->DC.LayoutType = backup_layout_type;
  1046. }
  1047. void ImGui::AlignTextToFramePadding()
  1048. {
  1049. ImGuiWindow* window = GetCurrentWindow();
  1050. if (window->SkipItems)
  1051. return;
  1052. ImGuiContext& g = *GImGui;
  1053. window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
  1054. window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
  1055. }
  1056. // Horizontal/vertical separating line
  1057. void ImGui::SeparatorEx(ImGuiSeparatorFlags flags)
  1058. {
  1059. ImGuiWindow* window = GetCurrentWindow();
  1060. if (window->SkipItems)
  1061. return;
  1062. ImGuiContext& g = *GImGui;
  1063. IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected
  1064. float thickness_draw = 1.0f;
  1065. float thickness_layout = 0.0f;
  1066. if (flags & ImGuiSeparatorFlags_Vertical)
  1067. {
  1068. // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout.
  1069. float y1 = window->DC.CursorPos.y;
  1070. float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
  1071. const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness_draw, y2));
  1072. ItemSize(ImVec2(thickness_layout, 0.0f));
  1073. if (!ItemAdd(bb, 0))
  1074. return;
  1075. // Draw
  1076. window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
  1077. if (g.LogEnabled)
  1078. LogText(" |");
  1079. }
  1080. else if (flags & ImGuiSeparatorFlags_Horizontal)
  1081. {
  1082. // Horizontal Separator
  1083. float x1 = window->Pos.x;
  1084. float x2 = window->Pos.x + window->Size.x;
  1085. if (!window->DC.GroupStack.empty())
  1086. x1 += window->DC.Indent.x;
  1087. ImGuiColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
  1088. if (columns)
  1089. PushColumnsBackground();
  1090. // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
  1091. const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness_draw));
  1092. ItemSize(ImVec2(0.0f, thickness_layout));
  1093. const bool item_visible = ItemAdd(bb, 0);
  1094. if (item_visible)
  1095. {
  1096. // Draw
  1097. window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
  1098. if (g.LogEnabled)
  1099. LogRenderedText(&bb.Min, "--------------------------------");
  1100. }
  1101. if (columns)
  1102. {
  1103. PopColumnsBackground();
  1104. columns->LineMinY = window->DC.CursorPos.y;
  1105. }
  1106. }
  1107. }
  1108. void ImGui::Separator()
  1109. {
  1110. ImGuiContext& g = *GImGui;
  1111. ImGuiWindow* window = g.CurrentWindow;
  1112. if (window->SkipItems)
  1113. return;
  1114. // Those flags should eventually be overridable by the user
  1115. ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
  1116. flags |= ImGuiSeparatorFlags_SpanAllColumns;
  1117. SeparatorEx(flags);
  1118. }
  1119. // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
  1120. bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
  1121. {
  1122. ImGuiContext& g = *GImGui;
  1123. ImGuiWindow* window = g.CurrentWindow;
  1124. const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
  1125. window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
  1126. bool item_add = ItemAdd(bb, id);
  1127. window->DC.ItemFlags = item_flags_backup;
  1128. if (!item_add)
  1129. return false;
  1130. bool hovered, held;
  1131. ImRect bb_interact = bb;
  1132. bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
  1133. ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
  1134. if (g.ActiveId != id)
  1135. SetItemAllowOverlap();
  1136. if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
  1137. SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
  1138. ImRect bb_render = bb;
  1139. if (held)
  1140. {
  1141. ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
  1142. float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
  1143. // Minimum pane size
  1144. float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
  1145. float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
  1146. if (mouse_delta < -size_1_maximum_delta)
  1147. mouse_delta = -size_1_maximum_delta;
  1148. if (mouse_delta > size_2_maximum_delta)
  1149. mouse_delta = size_2_maximum_delta;
  1150. // Apply resize
  1151. if (mouse_delta != 0.0f)
  1152. {
  1153. if (mouse_delta < 0.0f)
  1154. IM_ASSERT(*size1 + mouse_delta >= min_size1);
  1155. if (mouse_delta > 0.0f)
  1156. IM_ASSERT(*size2 - mouse_delta >= min_size2);
  1157. *size1 += mouse_delta;
  1158. *size2 -= mouse_delta;
  1159. bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
  1160. MarkItemEdited(id);
  1161. }
  1162. }
  1163. // Render
  1164. const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
  1165. window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
  1166. return held;
  1167. }
  1168. static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
  1169. {
  1170. const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
  1171. const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
  1172. if (int d = (int)(b->Width - a->Width))
  1173. return d;
  1174. return (b->Index - a->Index);
  1175. }
  1176. // Shrink excess width from a set of item, by removing width from the larger items first.
  1177. void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
  1178. {
  1179. if (count == 1)
  1180. {
  1181. items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
  1182. return;
  1183. }
  1184. ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
  1185. int count_same_width = 1;
  1186. while (width_excess > 0.0f && count_same_width < count)
  1187. {
  1188. while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
  1189. count_same_width++;
  1190. float max_width_to_remove_per_item = (count_same_width < count) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
  1191. float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
  1192. for (int item_n = 0; item_n < count_same_width; item_n++)
  1193. items[item_n].Width -= width_to_remove_per_item;
  1194. width_excess -= width_to_remove_per_item * count_same_width;
  1195. }
  1196. // Round width and redistribute remainder left-to-right (could make it an option of the function?)
  1197. // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
  1198. width_excess = 0.0f;
  1199. for (int n = 0; n < count; n++)
  1200. {
  1201. float width_rounded = ImFloor(items[n].Width);
  1202. width_excess += items[n].Width - width_rounded;
  1203. items[n].Width = width_rounded;
  1204. }
  1205. if (width_excess > 0.0f)
  1206. for (int n = 0; n < count; n++)
  1207. if (items[n].Index < (int)(width_excess + 0.01f))
  1208. items[n].Width += 1.0f;
  1209. }
  1210. //-------------------------------------------------------------------------
  1211. // [SECTION] Widgets: ComboBox
  1212. //-------------------------------------------------------------------------
  1213. // - BeginCombo()
  1214. // - EndCombo()
  1215. // - Combo()
  1216. //-------------------------------------------------------------------------
  1217. static float CalcMaxPopupHeightFromItemCount(int items_count)
  1218. {
  1219. ImGuiContext& g = *GImGui;
  1220. if (items_count <= 0)
  1221. return FLT_MAX;
  1222. return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
  1223. }
  1224. bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
  1225. {
  1226. // Always consume the SetNextWindowSizeConstraint() call in our early return paths
  1227. ImGuiContext& g = *GImGui;
  1228. bool has_window_size_constraint = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) != 0;
  1229. g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint;
  1230. ImGuiWindow* window = GetCurrentWindow();
  1231. if (window->SkipItems)
  1232. return false;
  1233. IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
  1234. const ImGuiStyle& style = g.Style;
  1235. const ImGuiID id = window->GetID(label);
  1236. const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
  1237. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  1238. const float expected_w = CalcItemWidth();
  1239. const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;
  1240. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
  1241. const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
  1242. ItemSize(total_bb, style.FramePadding.y);
  1243. if (!ItemAdd(total_bb, id, &frame_bb))
  1244. return false;
  1245. bool hovered, held;
  1246. bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
  1247. bool popup_open = IsPopupOpen(id);
  1248. const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
  1249. const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size);
  1250. RenderNavHighlight(frame_bb, id);
  1251. if (!(flags & ImGuiComboFlags_NoPreview))
  1252. window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Left);
  1253. if (!(flags & ImGuiComboFlags_NoArrowButton))
  1254. {
  1255. ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  1256. ImU32 text_col = GetColorU32(ImGuiCol_Text);
  1257. window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
  1258. if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x)
  1259. RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
  1260. }
  1261. RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
  1262. if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
  1263. RenderTextClipped(frame_bb.Min + style.FramePadding, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
  1264. if (label_size.x > 0)
  1265. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  1266. if ((pressed || g.NavActivateId == id) && !popup_open)
  1267. {
  1268. if (window->DC.NavLayerCurrent == 0)
  1269. window->NavLastIds[0] = id;
  1270. OpenPopupEx(id);
  1271. popup_open = true;
  1272. }
  1273. if (!popup_open)
  1274. return false;
  1275. if (has_window_size_constraint)
  1276. {
  1277. g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
  1278. g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
  1279. }
  1280. else
  1281. {
  1282. if ((flags & ImGuiComboFlags_HeightMask_) == 0)
  1283. flags |= ImGuiComboFlags_HeightRegular;
  1284. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
  1285. int popup_max_height_in_items = -1;
  1286. if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
  1287. else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
  1288. else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
  1289. SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
  1290. }
  1291. char name[16];
  1292. ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
  1293. // Peak into expected window size so we can position it
  1294. if (ImGuiWindow* popup_window = FindWindowByName(name))
  1295. if (popup_window->WasActive)
  1296. {
  1297. ImVec2 size_expected = CalcWindowExpectedSize(popup_window);
  1298. if (flags & ImGuiComboFlags_PopupAlignLeft)
  1299. popup_window->AutoPosLastDirection = ImGuiDir_Left;
  1300. ImRect r_outer = GetWindowAllowedExtentRect(popup_window);
  1301. ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
  1302. SetNextWindowPos(pos);
  1303. }
  1304. // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
  1305. ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
  1306. // Horizontally align ourselves with the framed text
  1307. PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
  1308. bool ret = Begin(name, NULL, window_flags);
  1309. PopStyleVar();
  1310. if (!ret)
  1311. {
  1312. EndPopup();
  1313. IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
  1314. return false;
  1315. }
  1316. return true;
  1317. }
  1318. void ImGui::EndCombo()
  1319. {
  1320. EndPopup();
  1321. }
  1322. // Getter for the old Combo() API: const char*[]
  1323. static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
  1324. {
  1325. const char* const* items = (const char* const*)data;
  1326. if (out_text)
  1327. *out_text = items[idx];
  1328. return true;
  1329. }
  1330. // Getter for the old Combo() API: "item1\0item2\0item3\0"
  1331. static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
  1332. {
  1333. // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
  1334. const char* items_separated_by_zeros = (const char*)data;
  1335. int items_count = 0;
  1336. const char* p = items_separated_by_zeros;
  1337. while (*p)
  1338. {
  1339. if (idx == items_count)
  1340. break;
  1341. p += strlen(p) + 1;
  1342. items_count++;
  1343. }
  1344. if (!*p)
  1345. return false;
  1346. if (out_text)
  1347. *out_text = p;
  1348. return true;
  1349. }
  1350. // Old API, prefer using BeginCombo() nowadays if you can.
  1351. bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
  1352. {
  1353. ImGuiContext& g = *GImGui;
  1354. // Call the getter to obtain the preview string which is a parameter to BeginCombo()
  1355. const char* preview_value = NULL;
  1356. if (*current_item >= 0 && *current_item < items_count)
  1357. items_getter(data, *current_item, &preview_value);
  1358. // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
  1359. if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
  1360. SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
  1361. if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
  1362. return false;
  1363. // Display items
  1364. // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
  1365. bool value_changed = false;
  1366. for (int i = 0; i < items_count; i++)
  1367. {
  1368. PushID((void*)(intptr_t)i);
  1369. const bool item_selected = (i == *current_item);
  1370. const char* item_text;
  1371. if (!items_getter(data, i, &item_text))
  1372. item_text = "*Unknown item*";
  1373. if (Selectable(item_text, item_selected))
  1374. {
  1375. value_changed = true;
  1376. *current_item = i;
  1377. }
  1378. if (item_selected)
  1379. SetItemDefaultFocus();
  1380. PopID();
  1381. }
  1382. EndCombo();
  1383. return value_changed;
  1384. }
  1385. // Combo box helper allowing to pass an array of strings.
  1386. bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
  1387. {
  1388. const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
  1389. return value_changed;
  1390. }
  1391. // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
  1392. bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
  1393. {
  1394. int items_count = 0;
  1395. const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
  1396. while (*p)
  1397. {
  1398. p += strlen(p) + 1;
  1399. items_count++;
  1400. }
  1401. bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
  1402. return value_changed;
  1403. }
  1404. //-------------------------------------------------------------------------
  1405. // [SECTION] Data Type and Data Formatting Helpers [Internal]
  1406. //-------------------------------------------------------------------------
  1407. // - PatchFormatStringFloatToInt()
  1408. // - DataTypeGetInfo()
  1409. // - DataTypeFormatString()
  1410. // - DataTypeApplyOp()
  1411. // - DataTypeApplyOpFromText()
  1412. // - GetMinimumStepAtDecimalPrecision
  1413. // - RoundScalarWithFormat<>()
  1414. //-------------------------------------------------------------------------
  1415. static const ImGuiDataTypeInfo GDataTypeInfo[] =
  1416. {
  1417. { sizeof(char), "%d", "%d" }, // ImGuiDataType_S8
  1418. { sizeof(unsigned char), "%u", "%u" },
  1419. { sizeof(short), "%d", "%d" }, // ImGuiDataType_S16
  1420. { sizeof(unsigned short), "%u", "%u" },
  1421. { sizeof(int), "%d", "%d" }, // ImGuiDataType_S32
  1422. { sizeof(unsigned int), "%u", "%u" },
  1423. #ifdef _MSC_VER
  1424. { sizeof(ImS64), "%I64d","%I64d" }, // ImGuiDataType_S64
  1425. { sizeof(ImU64), "%I64u","%I64u" },
  1426. #else
  1427. { sizeof(ImS64), "%lld", "%lld" }, // ImGuiDataType_S64
  1428. { sizeof(ImU64), "%llu", "%llu" },
  1429. #endif
  1430. { sizeof(float), "%f", "%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg)
  1431. { sizeof(double), "%f", "%lf" }, // ImGuiDataType_Double
  1432. };
  1433. IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
  1434. // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
  1435. // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
  1436. // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
  1437. static const char* PatchFormatStringFloatToInt(const char* fmt)
  1438. {
  1439. if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
  1440. return "%d";
  1441. const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
  1442. const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
  1443. if (fmt_end > fmt_start && fmt_end[-1] == 'f')
  1444. {
  1445. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
  1446. if (fmt_start == fmt && fmt_end[0] == 0)
  1447. return "%d";
  1448. ImGuiContext& g = *GImGui;
  1449. ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
  1450. return g.TempBuffer;
  1451. #else
  1452. IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
  1453. #endif
  1454. }
  1455. return fmt;
  1456. }
  1457. const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
  1458. {
  1459. IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
  1460. return &GDataTypeInfo[data_type];
  1461. }
  1462. int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
  1463. {
  1464. // Signedness doesn't matter when pushing integer arguments
  1465. if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
  1466. return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
  1467. if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
  1468. return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
  1469. if (data_type == ImGuiDataType_Float)
  1470. return ImFormatString(buf, buf_size, format, *(const float*)p_data);
  1471. if (data_type == ImGuiDataType_Double)
  1472. return ImFormatString(buf, buf_size, format, *(const double*)p_data);
  1473. if (data_type == ImGuiDataType_S8)
  1474. return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
  1475. if (data_type == ImGuiDataType_U8)
  1476. return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
  1477. if (data_type == ImGuiDataType_S16)
  1478. return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
  1479. if (data_type == ImGuiDataType_U16)
  1480. return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
  1481. IM_ASSERT(0);
  1482. return 0;
  1483. }
  1484. void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
  1485. {
  1486. IM_ASSERT(op == '+' || op == '-');
  1487. switch (data_type)
  1488. {
  1489. case ImGuiDataType_S8:
  1490. if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
  1491. if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); }
  1492. return;
  1493. case ImGuiDataType_U8:
  1494. if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
  1495. if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); }
  1496. return;
  1497. case ImGuiDataType_S16:
  1498. if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
  1499. if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
  1500. return;
  1501. case ImGuiDataType_U16:
  1502. if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
  1503. if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
  1504. return;
  1505. case ImGuiDataType_S32:
  1506. if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
  1507. if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
  1508. return;
  1509. case ImGuiDataType_U32:
  1510. if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
  1511. if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
  1512. return;
  1513. case ImGuiDataType_S64:
  1514. if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
  1515. if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
  1516. return;
  1517. case ImGuiDataType_U64:
  1518. if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
  1519. if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
  1520. return;
  1521. case ImGuiDataType_Float:
  1522. if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
  1523. if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
  1524. return;
  1525. case ImGuiDataType_Double:
  1526. if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
  1527. if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
  1528. return;
  1529. case ImGuiDataType_COUNT: break;
  1530. }
  1531. IM_ASSERT(0);
  1532. }
  1533. // User can input math operators (e.g. +100) to edit a numerical values.
  1534. // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
  1535. bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* p_data, const char* format)
  1536. {
  1537. while (ImCharIsBlankA(*buf))
  1538. buf++;
  1539. // We don't support '-' op because it would conflict with inputing negative value.
  1540. // Instead you can use +-100 to subtract from an existing value
  1541. char op = buf[0];
  1542. if (op == '+' || op == '*' || op == '/')
  1543. {
  1544. buf++;
  1545. while (ImCharIsBlankA(*buf))
  1546. buf++;
  1547. }
  1548. else
  1549. {
  1550. op = 0;
  1551. }
  1552. if (!buf[0])
  1553. return false;
  1554. // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
  1555. IM_ASSERT(data_type < ImGuiDataType_COUNT);
  1556. int data_backup[2];
  1557. const ImGuiDataTypeInfo* type_info = ImGui::DataTypeGetInfo(data_type);
  1558. IM_ASSERT(type_info->Size <= sizeof(data_backup));
  1559. memcpy(data_backup, p_data, type_info->Size);
  1560. if (format == NULL)
  1561. format = type_info->ScanFmt;
  1562. // FIXME-LEGACY: The aim is to remove those operators and write a proper expression evaluator at some point..
  1563. int arg1i = 0;
  1564. if (data_type == ImGuiDataType_S32)
  1565. {
  1566. int* v = (int*)p_data;
  1567. int arg0i = *v;
  1568. float arg1f = 0.0f;
  1569. if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
  1570. return false;
  1571. // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
  1572. if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
  1573. else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
  1574. else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
  1575. else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant
  1576. }
  1577. else if (data_type == ImGuiDataType_Float)
  1578. {
  1579. // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
  1580. format = "%f";
  1581. float* v = (float*)p_data;
  1582. float arg0f = *v, arg1f = 0.0f;
  1583. if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
  1584. return false;
  1585. if (sscanf(buf, format, &arg1f) < 1)
  1586. return false;
  1587. if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
  1588. else if (op == '*') { *v = arg0f * arg1f; } // Multiply
  1589. else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
  1590. else { *v = arg1f; } // Assign constant
  1591. }
  1592. else if (data_type == ImGuiDataType_Double)
  1593. {
  1594. format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
  1595. double* v = (double*)p_data;
  1596. double arg0f = *v, arg1f = 0.0;
  1597. if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
  1598. return false;
  1599. if (sscanf(buf, format, &arg1f) < 1)
  1600. return false;
  1601. if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
  1602. else if (op == '*') { *v = arg0f * arg1f; } // Multiply
  1603. else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
  1604. else { *v = arg1f; } // Assign constant
  1605. }
  1606. else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
  1607. {
  1608. // All other types assign constant
  1609. // We don't bother handling support for legacy operators since they are a little too crappy. Instead we will later implement a proper expression evaluator in the future.
  1610. sscanf(buf, format, p_data);
  1611. }
  1612. else
  1613. {
  1614. // Small types need a 32-bit buffer to receive the result from scanf()
  1615. int v32;
  1616. sscanf(buf, format, &v32);
  1617. if (data_type == ImGuiDataType_S8)
  1618. *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
  1619. else if (data_type == ImGuiDataType_U8)
  1620. *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
  1621. else if (data_type == ImGuiDataType_S16)
  1622. *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
  1623. else if (data_type == ImGuiDataType_U16)
  1624. *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
  1625. else
  1626. IM_ASSERT(0);
  1627. }
  1628. return memcmp(data_backup, p_data, type_info->Size) != 0;
  1629. }
  1630. static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
  1631. {
  1632. static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
  1633. if (decimal_precision < 0)
  1634. return FLT_MIN;
  1635. return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
  1636. }
  1637. template<typename TYPE>
  1638. static const char* ImAtoi(const char* src, TYPE* output)
  1639. {
  1640. int negative = 0;
  1641. if (*src == '-') { negative = 1; src++; }
  1642. if (*src == '+') { src++; }
  1643. TYPE v = 0;
  1644. while (*src >= '0' && *src <= '9')
  1645. v = (v * 10) + (*src++ - '0');
  1646. *output = negative ? -v : v;
  1647. return src;
  1648. }
  1649. template<typename TYPE, typename SIGNEDTYPE>
  1650. TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
  1651. {
  1652. const char* fmt_start = ImParseFormatFindStart(format);
  1653. if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
  1654. return v;
  1655. char v_str[64];
  1656. ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
  1657. const char* p = v_str;
  1658. while (*p == ' ')
  1659. p++;
  1660. if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
  1661. v = (TYPE)ImAtof(p);
  1662. else
  1663. ImAtoi(p, (SIGNEDTYPE*)&v);
  1664. return v;
  1665. }
  1666. //-------------------------------------------------------------------------
  1667. // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
  1668. //-------------------------------------------------------------------------
  1669. // - DragBehaviorT<>() [Internal]
  1670. // - DragBehavior() [Internal]
  1671. // - DragScalar()
  1672. // - DragScalarN()
  1673. // - DragFloat()
  1674. // - DragFloat2()
  1675. // - DragFloat3()
  1676. // - DragFloat4()
  1677. // - DragFloatRange2()
  1678. // - DragInt()
  1679. // - DragInt2()
  1680. // - DragInt3()
  1681. // - DragInt4()
  1682. // - DragIntRange2()
  1683. //-------------------------------------------------------------------------
  1684. // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
  1685. template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
  1686. bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiDragFlags flags)
  1687. {
  1688. ImGuiContext& g = *GImGui;
  1689. const ImGuiAxis axis = (flags & ImGuiDragFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
  1690. const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
  1691. const bool is_clamped = (v_min < v_max);
  1692. const bool is_power = (power != 1.0f && is_decimal && is_clamped && (v_max - v_min < FLT_MAX));
  1693. const bool is_locked = (v_min > v_max);
  1694. if (is_locked)
  1695. return false;
  1696. // Default tweak speed
  1697. if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
  1698. v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
  1699. // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
  1700. float adjust_delta = 0.0f;
  1701. if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
  1702. {
  1703. adjust_delta = g.IO.MouseDelta[axis];
  1704. if (g.IO.KeyAlt)
  1705. adjust_delta *= 1.0f / 100.0f;
  1706. if (g.IO.KeyShift)
  1707. adjust_delta *= 10.0f;
  1708. }
  1709. else if (g.ActiveIdSource == ImGuiInputSource_Nav)
  1710. {
  1711. int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
  1712. adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
  1713. v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
  1714. }
  1715. adjust_delta *= v_speed;
  1716. // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
  1717. if (axis == ImGuiAxis_Y)
  1718. adjust_delta = -adjust_delta;
  1719. // Clear current value on activation
  1720. // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
  1721. bool is_just_activated = g.ActiveIdIsJustActivated;
  1722. bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
  1723. bool is_drag_direction_change_with_power = is_power && ((adjust_delta < 0 && g.DragCurrentAccum > 0) || (adjust_delta > 0 && g.DragCurrentAccum < 0));
  1724. if (is_just_activated || is_already_past_limits_and_pushing_outward || is_drag_direction_change_with_power)
  1725. {
  1726. g.DragCurrentAccum = 0.0f;
  1727. g.DragCurrentAccumDirty = false;
  1728. }
  1729. else if (adjust_delta != 0.0f)
  1730. {
  1731. g.DragCurrentAccum += adjust_delta;
  1732. g.DragCurrentAccumDirty = true;
  1733. }
  1734. if (!g.DragCurrentAccumDirty)
  1735. return false;
  1736. TYPE v_cur = *v;
  1737. FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
  1738. if (is_power)
  1739. {
  1740. // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
  1741. FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
  1742. FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
  1743. v_cur = v_min + (SIGNEDTYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
  1744. v_old_ref_for_accum_remainder = v_old_norm_curved;
  1745. }
  1746. else
  1747. {
  1748. v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
  1749. }
  1750. // Round to user desired precision based on format string
  1751. v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
  1752. // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
  1753. g.DragCurrentAccumDirty = false;
  1754. if (is_power)
  1755. {
  1756. FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
  1757. g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
  1758. }
  1759. else
  1760. {
  1761. g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
  1762. }
  1763. // Lose zero sign for float/double
  1764. if (v_cur == (TYPE)-0)
  1765. v_cur = (TYPE)0;
  1766. // Clamp values (+ handle overflow/wrap-around for integer types)
  1767. if (*v != v_cur && is_clamped)
  1768. {
  1769. if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))
  1770. v_cur = v_min;
  1771. if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))
  1772. v_cur = v_max;
  1773. }
  1774. // Apply result
  1775. if (*v == v_cur)
  1776. return false;
  1777. *v = v_cur;
  1778. return true;
  1779. }
  1780. bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, float power, ImGuiDragFlags flags)
  1781. {
  1782. ImGuiContext& g = *GImGui;
  1783. if (g.ActiveId == id)
  1784. {
  1785. if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
  1786. ClearActiveID();
  1787. else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
  1788. ClearActiveID();
  1789. }
  1790. if (g.ActiveId != id)
  1791. return false;
  1792. switch (data_type)
  1793. {
  1794. case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, power, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
  1795. case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN, p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, power, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
  1796. case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, power, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
  1797. case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, power, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
  1798. case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v, v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, power, flags);
  1799. case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v, v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, power, flags);
  1800. case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v, v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, power, flags);
  1801. case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v, v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, power, flags);
  1802. case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)p_v, v_speed, p_min ? *(const float* )p_min : -FLT_MAX, p_max ? *(const float* )p_max : FLT_MAX, format, power, flags);
  1803. case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX, p_max ? *(const double*)p_max : DBL_MAX, format, power, flags);
  1804. case ImGuiDataType_COUNT: break;
  1805. }
  1806. IM_ASSERT(0);
  1807. return false;
  1808. }
  1809. // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.
  1810. // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
  1811. bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
  1812. {
  1813. ImGuiWindow* window = GetCurrentWindow();
  1814. if (window->SkipItems)
  1815. return false;
  1816. if (power != 1.0f)
  1817. IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds
  1818. ImGuiContext& g = *GImGui;
  1819. const ImGuiStyle& style = g.Style;
  1820. const ImGuiID id = window->GetID(label);
  1821. const float w = CalcItemWidth();
  1822. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  1823. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
  1824. const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
  1825. ItemSize(total_bb, style.FramePadding.y);
  1826. if (!ItemAdd(total_bb, id, &frame_bb))
  1827. return false;
  1828. // Default format string when passing NULL
  1829. if (format == NULL)
  1830. format = DataTypeGetInfo(data_type)->PrintFmt;
  1831. else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
  1832. format = PatchFormatStringFloatToInt(format);
  1833. // Tabbing or CTRL-clicking on Drag turns it into an input box
  1834. const bool hovered = ItemHoverable(frame_bb, id);
  1835. bool temp_input_is_active = TempInputTextIsActive(id);
  1836. bool temp_input_start = false;
  1837. if (!temp_input_is_active)
  1838. {
  1839. const bool focus_requested = FocusableItemRegister(window, id);
  1840. const bool clicked = (hovered && g.IO.MouseClicked[0]);
  1841. const bool double_clicked = (hovered && g.IO.MouseDoubleClicked[0]);
  1842. if (focus_requested || clicked || double_clicked || g.NavActivateId == id || g.NavInputId == id)
  1843. {
  1844. SetActiveID(id, window);
  1845. SetFocusID(id, window);
  1846. FocusWindow(window);
  1847. g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
  1848. if (focus_requested || (clicked && g.IO.KeyCtrl) || double_clicked || g.NavInputId == id)
  1849. {
  1850. temp_input_start = true;
  1851. FocusableItemUnregister(window);
  1852. }
  1853. }
  1854. }
  1855. if (temp_input_is_active || temp_input_start)
  1856. return TempInputTextScalar(frame_bb, id, label, data_type, p_data, format);
  1857. // Draw frame
  1858. const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
  1859. RenderNavHighlight(frame_bb, id);
  1860. RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
  1861. // Drag behavior
  1862. const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, power, ImGuiDragFlags_None);
  1863. if (value_changed)
  1864. MarkItemEdited(id);
  1865. // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
  1866. char value_buf[64];
  1867. const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
  1868. RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
  1869. if (label_size.x > 0.0f)
  1870. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  1871. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
  1872. return value_changed;
  1873. }
  1874. bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
  1875. {
  1876. ImGuiWindow* window = GetCurrentWindow();
  1877. if (window->SkipItems)
  1878. return false;
  1879. ImGuiContext& g = *GImGui;
  1880. bool value_changed = false;
  1881. BeginGroup();
  1882. PushID(label);
  1883. PushMultiItemsWidths(components, CalcItemWidth());
  1884. size_t type_size = GDataTypeInfo[data_type].Size;
  1885. for (int i = 0; i < components; i++)
  1886. {
  1887. PushID(i);
  1888. if (i > 0)
  1889. SameLine(0, g.Style.ItemInnerSpacing.x);
  1890. value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, power);
  1891. PopID();
  1892. PopItemWidth();
  1893. p_data = (void*)((char*)p_data + type_size);
  1894. }
  1895. PopID();
  1896. const char* label_end = FindRenderedTextEnd(label);
  1897. if (label != label_end)
  1898. {
  1899. SameLine(0, g.Style.ItemInnerSpacing.x);
  1900. TextEx(label, label_end);
  1901. }
  1902. EndGroup();
  1903. return value_changed;
  1904. }
  1905. bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
  1906. {
  1907. return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
  1908. }
  1909. bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
  1910. {
  1911. return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
  1912. }
  1913. bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
  1914. {
  1915. return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
  1916. }
  1917. bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
  1918. {
  1919. return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
  1920. }
  1921. bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
  1922. {
  1923. ImGuiWindow* window = GetCurrentWindow();
  1924. if (window->SkipItems)
  1925. return false;
  1926. ImGuiContext& g = *GImGui;
  1927. PushID(label);
  1928. BeginGroup();
  1929. PushMultiItemsWidths(2, CalcItemWidth());
  1930. bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
  1931. PopItemWidth();
  1932. SameLine(0, g.Style.ItemInnerSpacing.x);
  1933. value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
  1934. PopItemWidth();
  1935. SameLine(0, g.Style.ItemInnerSpacing.x);
  1936. TextEx(label, FindRenderedTextEnd(label));
  1937. EndGroup();
  1938. PopID();
  1939. return value_changed;
  1940. }
  1941. // NB: v_speed is float to allow adjusting the drag speed with more precision
  1942. bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
  1943. {
  1944. return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
  1945. }
  1946. bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
  1947. {
  1948. return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
  1949. }
  1950. bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
  1951. {
  1952. return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
  1953. }
  1954. bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
  1955. {
  1956. return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
  1957. }
  1958. bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
  1959. {
  1960. ImGuiWindow* window = GetCurrentWindow();
  1961. if (window->SkipItems)
  1962. return false;
  1963. ImGuiContext& g = *GImGui;
  1964. PushID(label);
  1965. BeginGroup();
  1966. PushMultiItemsWidths(2, CalcItemWidth());
  1967. bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
  1968. PopItemWidth();
  1969. SameLine(0, g.Style.ItemInnerSpacing.x);
  1970. value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
  1971. PopItemWidth();
  1972. SameLine(0, g.Style.ItemInnerSpacing.x);
  1973. TextEx(label, FindRenderedTextEnd(label));
  1974. EndGroup();
  1975. PopID();
  1976. return value_changed;
  1977. }
  1978. //-------------------------------------------------------------------------
  1979. // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
  1980. //-------------------------------------------------------------------------
  1981. // - SliderBehaviorT<>() [Internal]
  1982. // - SliderBehavior() [Internal]
  1983. // - SliderScalar()
  1984. // - SliderScalarN()
  1985. // - SliderFloat()
  1986. // - SliderFloat2()
  1987. // - SliderFloat3()
  1988. // - SliderFloat4()
  1989. // - SliderAngle()
  1990. // - SliderInt()
  1991. // - SliderInt2()
  1992. // - SliderInt3()
  1993. // - SliderInt4()
  1994. // - VSliderScalar()
  1995. // - VSliderFloat()
  1996. // - VSliderInt()
  1997. //-------------------------------------------------------------------------
  1998. template<typename TYPE, typename FLOATTYPE>
  1999. float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
  2000. {
  2001. if (v_min == v_max)
  2002. return 0.0f;
  2003. const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
  2004. const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
  2005. if (is_power)
  2006. {
  2007. if (v_clamped < 0.0f)
  2008. {
  2009. const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
  2010. return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
  2011. }
  2012. else
  2013. {
  2014. const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
  2015. return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
  2016. }
  2017. }
  2018. // Linear slider
  2019. return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
  2020. }
  2021. // FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
  2022. template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
  2023. bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
  2024. {
  2025. ImGuiContext& g = *GImGui;
  2026. const ImGuiStyle& style = g.Style;
  2027. const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
  2028. const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
  2029. const bool is_power = (power != 1.0f) && is_decimal;
  2030. const float grab_padding = 2.0f;
  2031. const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
  2032. float grab_sz = style.GrabMinSize;
  2033. SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
  2034. if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows
  2035. grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
  2036. grab_sz = ImMin(grab_sz, slider_sz);
  2037. const float slider_usable_sz = slider_sz - grab_sz;
  2038. const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
  2039. const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
  2040. // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
  2041. float linear_zero_pos; // 0.0->1.0f
  2042. if (is_power && v_min * v_max < 0.0f)
  2043. {
  2044. // Different sign
  2045. const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f / power);
  2046. const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f / power);
  2047. linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
  2048. }
  2049. else
  2050. {
  2051. // Same sign
  2052. linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
  2053. }
  2054. // Process interacting with the slider
  2055. bool value_changed = false;
  2056. if (g.ActiveId == id)
  2057. {
  2058. bool set_new_value = false;
  2059. float clicked_t = 0.0f;
  2060. if (g.ActiveIdSource == ImGuiInputSource_Mouse)
  2061. {
  2062. if (!g.IO.MouseDown[0])
  2063. {
  2064. ClearActiveID();
  2065. }
  2066. else
  2067. {
  2068. const float mouse_abs_pos = g.IO.MousePos[axis];
  2069. clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
  2070. if (axis == ImGuiAxis_Y)
  2071. clicked_t = 1.0f - clicked_t;
  2072. set_new_value = true;
  2073. }
  2074. }
  2075. else if (g.ActiveIdSource == ImGuiInputSource_Nav)
  2076. {
  2077. const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
  2078. float delta = (axis == ImGuiAxis_X) ? delta2.x : -delta2.y;
  2079. if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
  2080. {
  2081. ClearActiveID();
  2082. }
  2083. else if (delta != 0.0f)
  2084. {
  2085. clicked_t = SliderCalcRatioFromValueT<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
  2086. const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
  2087. if ((decimal_precision > 0) || is_power)
  2088. {
  2089. delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
  2090. if (IsNavInputDown(ImGuiNavInput_TweakSlow))
  2091. delta /= 10.0f;
  2092. }
  2093. else
  2094. {
  2095. if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
  2096. delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
  2097. else
  2098. delta /= 100.0f;
  2099. }
  2100. if (IsNavInputDown(ImGuiNavInput_TweakFast))
  2101. delta *= 10.0f;
  2102. set_new_value = true;
  2103. if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
  2104. set_new_value = false;
  2105. else
  2106. clicked_t = ImSaturate(clicked_t + delta);
  2107. }
  2108. }
  2109. if (set_new_value)
  2110. {
  2111. TYPE v_new;
  2112. if (is_power)
  2113. {
  2114. // Account for power curve scale on both sides of the zero
  2115. if (clicked_t < linear_zero_pos)
  2116. {
  2117. // Negative: rescale to the negative range before powering
  2118. float a = 1.0f - (clicked_t / linear_zero_pos);
  2119. a = ImPow(a, power);
  2120. v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
  2121. }
  2122. else
  2123. {
  2124. // Positive: rescale to the positive range before powering
  2125. float a;
  2126. if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
  2127. a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
  2128. else
  2129. a = clicked_t;
  2130. a = ImPow(a, power);
  2131. v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
  2132. }
  2133. }
  2134. else
  2135. {
  2136. // Linear slider
  2137. if (is_decimal)
  2138. {
  2139. v_new = ImLerp(v_min, v_max, clicked_t);
  2140. }
  2141. else
  2142. {
  2143. // For integer values we want the clicking position to match the grab box so we round above
  2144. // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
  2145. FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
  2146. TYPE v_new_off_floor = (TYPE)(v_new_off_f);
  2147. TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
  2148. if (v_new_off_floor < v_new_off_round)
  2149. v_new = v_min + v_new_off_round;
  2150. else
  2151. v_new = v_min + v_new_off_floor;
  2152. }
  2153. }
  2154. // Round to user desired precision based on format string
  2155. v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
  2156. // Apply result
  2157. if (*v != v_new)
  2158. {
  2159. *v = v_new;
  2160. value_changed = true;
  2161. }
  2162. }
  2163. }
  2164. if (slider_sz < 1.0f)
  2165. {
  2166. *out_grab_bb = ImRect(bb.Min, bb.Min);
  2167. }
  2168. else
  2169. {
  2170. // Output grab position so it can be displayed by the caller
  2171. float grab_t = SliderCalcRatioFromValueT<TYPE, FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
  2172. if (axis == ImGuiAxis_Y)
  2173. grab_t = 1.0f - grab_t;
  2174. const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
  2175. if (axis == ImGuiAxis_X)
  2176. *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding);
  2177. else
  2178. *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f);
  2179. }
  2180. return value_changed;
  2181. }
  2182. // For 32-bit and larger types, slider bounds are limited to half the natural type range.
  2183. // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
  2184. // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
  2185. bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb)
  2186. {
  2187. switch (data_type)
  2188. {
  2189. case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, power, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
  2190. case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min, *(const ImU8*)p_max, format, power, flags, out_grab_bb); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
  2191. case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, power, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
  2192. case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, power, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
  2193. case ImGuiDataType_S32:
  2194. IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN/2 && *(const ImS32*)p_max <= IM_S32_MAX/2);
  2195. return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v, *(const ImS32*)p_min, *(const ImS32*)p_max, format, power, flags, out_grab_bb);
  2196. case ImGuiDataType_U32:
  2197. IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX/2);
  2198. return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v, *(const ImU32*)p_min, *(const ImU32*)p_max, format, power, flags, out_grab_bb);
  2199. case ImGuiDataType_S64:
  2200. IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN/2 && *(const ImS64*)p_max <= IM_S64_MAX/2);
  2201. return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v, *(const ImS64*)p_min, *(const ImS64*)p_max, format, power, flags, out_grab_bb);
  2202. case ImGuiDataType_U64:
  2203. IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX/2);
  2204. return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v, *(const ImU64*)p_min, *(const ImU64*)p_max, format, power, flags, out_grab_bb);
  2205. case ImGuiDataType_Float:
  2206. IM_ASSERT(*(const float*)p_min >= -FLT_MAX/2.0f && *(const float*)p_max <= FLT_MAX/2.0f);
  2207. return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v, *(const float*)p_min, *(const float*)p_max, format, power, flags, out_grab_bb);
  2208. case ImGuiDataType_Double:
  2209. IM_ASSERT(*(const double*)p_min >= -DBL_MAX/2.0f && *(const double*)p_max <= DBL_MAX/2.0f);
  2210. return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, power, flags, out_grab_bb);
  2211. case ImGuiDataType_COUNT: break;
  2212. }
  2213. IM_ASSERT(0);
  2214. return false;
  2215. }
  2216. // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
  2217. // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
  2218. bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
  2219. {
  2220. ImGuiWindow* window = GetCurrentWindow();
  2221. if (window->SkipItems)
  2222. return false;
  2223. ImGuiContext& g = *GImGui;
  2224. const ImGuiStyle& style = g.Style;
  2225. const ImGuiID id = window->GetID(label);
  2226. const float w = CalcItemWidth();
  2227. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  2228. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
  2229. const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
  2230. ItemSize(total_bb, style.FramePadding.y);
  2231. if (!ItemAdd(total_bb, id, &frame_bb))
  2232. return false;
  2233. // Default format string when passing NULL
  2234. if (format == NULL)
  2235. format = DataTypeGetInfo(data_type)->PrintFmt;
  2236. else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
  2237. format = PatchFormatStringFloatToInt(format);
  2238. // Tabbing or CTRL-clicking on Slider turns it into an input box
  2239. const bool hovered = ItemHoverable(frame_bb, id);
  2240. bool temp_input_is_active = TempInputTextIsActive(id);
  2241. bool temp_input_start = false;
  2242. if (!temp_input_is_active)
  2243. {
  2244. const bool focus_requested = FocusableItemRegister(window, id);
  2245. const bool clicked = (hovered && g.IO.MouseClicked[0]);
  2246. if (focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id)
  2247. {
  2248. SetActiveID(id, window);
  2249. SetFocusID(id, window);
  2250. FocusWindow(window);
  2251. g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
  2252. if (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id)
  2253. {
  2254. temp_input_start = true;
  2255. FocusableItemUnregister(window);
  2256. }
  2257. }
  2258. }
  2259. if (temp_input_is_active || temp_input_start)
  2260. return TempInputTextScalar(frame_bb, id, label, data_type, p_data, format);
  2261. // Draw frame
  2262. const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
  2263. RenderNavHighlight(frame_bb, id);
  2264. RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
  2265. // Slider behavior
  2266. ImRect grab_bb;
  2267. const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
  2268. if (value_changed)
  2269. MarkItemEdited(id);
  2270. // Render grab
  2271. if (grab_bb.Max.x > grab_bb.Min.x)
  2272. window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
  2273. // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
  2274. char value_buf[64];
  2275. const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
  2276. RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
  2277. if (label_size.x > 0.0f)
  2278. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  2279. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
  2280. return value_changed;
  2281. }
  2282. // Add multiple sliders on 1 line for compact edition of multiple components
  2283. bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
  2284. {
  2285. ImGuiWindow* window = GetCurrentWindow();
  2286. if (window->SkipItems)
  2287. return false;
  2288. ImGuiContext& g = *GImGui;
  2289. bool value_changed = false;
  2290. BeginGroup();
  2291. PushID(label);
  2292. PushMultiItemsWidths(components, CalcItemWidth());
  2293. size_t type_size = GDataTypeInfo[data_type].Size;
  2294. for (int i = 0; i < components; i++)
  2295. {
  2296. PushID(i);
  2297. if (i > 0)
  2298. SameLine(0, g.Style.ItemInnerSpacing.x);
  2299. value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, power);
  2300. PopID();
  2301. PopItemWidth();
  2302. v = (void*)((char*)v + type_size);
  2303. }
  2304. PopID();
  2305. const char* label_end = FindRenderedTextEnd(label);
  2306. if (label != label_end)
  2307. {
  2308. SameLine(0, g.Style.ItemInnerSpacing.x);
  2309. TextEx(label, label_end);
  2310. }
  2311. EndGroup();
  2312. return value_changed;
  2313. }
  2314. bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
  2315. {
  2316. return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
  2317. }
  2318. bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
  2319. {
  2320. return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
  2321. }
  2322. bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
  2323. {
  2324. return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
  2325. }
  2326. bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
  2327. {
  2328. return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
  2329. }
  2330. bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)
  2331. {
  2332. if (format == NULL)
  2333. format = "%.0f deg";
  2334. float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
  2335. bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, 1.0f);
  2336. *v_rad = v_deg * (2*IM_PI) / 360.0f;
  2337. return value_changed;
  2338. }
  2339. bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
  2340. {
  2341. return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
  2342. }
  2343. bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
  2344. {
  2345. return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
  2346. }
  2347. bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
  2348. {
  2349. return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
  2350. }
  2351. bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
  2352. {
  2353. return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
  2354. }
  2355. bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
  2356. {
  2357. ImGuiWindow* window = GetCurrentWindow();
  2358. if (window->SkipItems)
  2359. return false;
  2360. ImGuiContext& g = *GImGui;
  2361. const ImGuiStyle& style = g.Style;
  2362. const ImGuiID id = window->GetID(label);
  2363. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  2364. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
  2365. const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
  2366. ItemSize(bb, style.FramePadding.y);
  2367. if (!ItemAdd(frame_bb, id))
  2368. return false;
  2369. // Default format string when passing NULL
  2370. if (format == NULL)
  2371. format = DataTypeGetInfo(data_type)->PrintFmt;
  2372. else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
  2373. format = PatchFormatStringFloatToInt(format);
  2374. const bool hovered = ItemHoverable(frame_bb, id);
  2375. if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
  2376. {
  2377. SetActiveID(id, window);
  2378. SetFocusID(id, window);
  2379. FocusWindow(window);
  2380. g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
  2381. }
  2382. // Draw frame
  2383. const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
  2384. RenderNavHighlight(frame_bb, id);
  2385. RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
  2386. // Slider behavior
  2387. ImRect grab_bb;
  2388. const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
  2389. if (value_changed)
  2390. MarkItemEdited(id);
  2391. // Render grab
  2392. if (grab_bb.Max.y > grab_bb.Min.y)
  2393. window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
  2394. // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
  2395. // For the vertical slider we allow centered text to overlap the frame padding
  2396. char value_buf[64];
  2397. const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
  2398. RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f));
  2399. if (label_size.x > 0.0f)
  2400. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  2401. return value_changed;
  2402. }
  2403. bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
  2404. {
  2405. return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
  2406. }
  2407. bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
  2408. {
  2409. return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
  2410. }
  2411. //-------------------------------------------------------------------------
  2412. // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
  2413. //-------------------------------------------------------------------------
  2414. // - ImParseFormatFindStart() [Internal]
  2415. // - ImParseFormatFindEnd() [Internal]
  2416. // - ImParseFormatTrimDecorations() [Internal]
  2417. // - ImParseFormatPrecision() [Internal]
  2418. // - TempInputTextScalar() [Internal]
  2419. // - InputScalar()
  2420. // - InputScalarN()
  2421. // - InputFloat()
  2422. // - InputFloat2()
  2423. // - InputFloat3()
  2424. // - InputFloat4()
  2425. // - InputInt()
  2426. // - InputInt2()
  2427. // - InputInt3()
  2428. // - InputInt4()
  2429. // - InputDouble()
  2430. //-------------------------------------------------------------------------
  2431. // We don't use strchr() because our strings are usually very short and often start with '%'
  2432. const char* ImParseFormatFindStart(const char* fmt)
  2433. {
  2434. while (char c = fmt[0])
  2435. {
  2436. if (c == '%' && fmt[1] != '%')
  2437. return fmt;
  2438. else if (c == '%')
  2439. fmt++;
  2440. fmt++;
  2441. }
  2442. return fmt;
  2443. }
  2444. const char* ImParseFormatFindEnd(const char* fmt)
  2445. {
  2446. // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
  2447. if (fmt[0] != '%')
  2448. return fmt;
  2449. const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
  2450. const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
  2451. for (char c; (c = *fmt) != 0; fmt++)
  2452. {
  2453. if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
  2454. return fmt + 1;
  2455. if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
  2456. return fmt + 1;
  2457. }
  2458. return fmt;
  2459. }
  2460. // Extract the format out of a format string with leading or trailing decorations
  2461. // fmt = "blah blah" -> return fmt
  2462. // fmt = "%.3f" -> return fmt
  2463. // fmt = "hello %.3f" -> return fmt + 6
  2464. // fmt = "%.3f hello" -> return buf written with "%.3f"
  2465. const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
  2466. {
  2467. const char* fmt_start = ImParseFormatFindStart(fmt);
  2468. if (fmt_start[0] != '%')
  2469. return fmt;
  2470. const char* fmt_end = ImParseFormatFindEnd(fmt_start);
  2471. if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
  2472. return fmt_start;
  2473. ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
  2474. return buf;
  2475. }
  2476. // Parse display precision back from the display format string
  2477. // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
  2478. int ImParseFormatPrecision(const char* fmt, int default_precision)
  2479. {
  2480. fmt = ImParseFormatFindStart(fmt);
  2481. if (fmt[0] != '%')
  2482. return default_precision;
  2483. fmt++;
  2484. while (*fmt >= '0' && *fmt <= '9')
  2485. fmt++;
  2486. int precision = INT_MAX;
  2487. if (*fmt == '.')
  2488. {
  2489. fmt = ImAtoi<int>(fmt + 1, &precision);
  2490. if (precision < 0 || precision > 99)
  2491. precision = default_precision;
  2492. }
  2493. if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
  2494. precision = -1;
  2495. if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
  2496. precision = -1;
  2497. return (precision == INT_MAX) ? default_precision : precision;
  2498. }
  2499. // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
  2500. // FIXME: Facilitate using this in variety of other situations.
  2501. bool ImGui::TempInputTextScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format)
  2502. {
  2503. ImGuiContext& g = *GImGui;
  2504. // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
  2505. // We clear ActiveID on the first frame to allow the InputText() taking it back.
  2506. const bool init = (g.TempInputTextId != id);
  2507. if (init)
  2508. ClearActiveID();
  2509. char fmt_buf[32];
  2510. char data_buf[32];
  2511. format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
  2512. DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
  2513. ImStrTrimBlanks(data_buf);
  2514. g.CurrentWindow->DC.CursorPos = bb.Min;
  2515. ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
  2516. flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
  2517. bool value_changed = InputTextEx(label, NULL, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
  2518. if (init)
  2519. {
  2520. // First frame we started displaying the InputText widget, we expect it to take the active id.
  2521. IM_ASSERT(g.ActiveId == id);
  2522. g.TempInputTextId = g.ActiveId;
  2523. }
  2524. if (value_changed)
  2525. {
  2526. value_changed = DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL);
  2527. if (value_changed)
  2528. MarkItemEdited(id);
  2529. }
  2530. return value_changed;
  2531. }
  2532. // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.
  2533. // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
  2534. bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
  2535. {
  2536. ImGuiWindow* window = GetCurrentWindow();
  2537. if (window->SkipItems)
  2538. return false;
  2539. ImGuiContext& g = *GImGui;
  2540. ImGuiStyle& style = g.Style;
  2541. if (format == NULL)
  2542. format = DataTypeGetInfo(data_type)->PrintFmt;
  2543. char buf[64];
  2544. DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
  2545. bool value_changed = false;
  2546. if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
  2547. flags |= ImGuiInputTextFlags_CharsDecimal;
  2548. flags |= ImGuiInputTextFlags_AutoSelectAll;
  2549. flags |= ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselve by comparing the actual data rather than the string.
  2550. if (p_step != NULL)
  2551. {
  2552. const float button_size = GetFrameHeight();
  2553. BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
  2554. PushID(label);
  2555. SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
  2556. if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
  2557. value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
  2558. // Step buttons
  2559. const ImVec2 backup_frame_padding = style.FramePadding;
  2560. style.FramePadding.x = style.FramePadding.y;
  2561. ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups;
  2562. if (flags & ImGuiInputTextFlags_ReadOnly)
  2563. button_flags |= ImGuiButtonFlags_Disabled;
  2564. SameLine(0, style.ItemInnerSpacing.x);
  2565. if (ButtonEx("-", ImVec2(button_size, button_size), button_flags))
  2566. {
  2567. DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
  2568. value_changed = true;
  2569. }
  2570. SameLine(0, style.ItemInnerSpacing.x);
  2571. if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
  2572. {
  2573. DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
  2574. value_changed = true;
  2575. }
  2576. const char* label_end = FindRenderedTextEnd(label);
  2577. if (label != label_end)
  2578. {
  2579. SameLine(0, style.ItemInnerSpacing.x);
  2580. TextEx(label, label_end);
  2581. }
  2582. style.FramePadding = backup_frame_padding;
  2583. PopID();
  2584. EndGroup();
  2585. }
  2586. else
  2587. {
  2588. if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
  2589. value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, p_data, format);
  2590. }
  2591. if (value_changed)
  2592. MarkItemEdited(window->DC.LastItemId);
  2593. return value_changed;
  2594. }
  2595. bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
  2596. {
  2597. ImGuiWindow* window = GetCurrentWindow();
  2598. if (window->SkipItems)
  2599. return false;
  2600. ImGuiContext& g = *GImGui;
  2601. bool value_changed = false;
  2602. BeginGroup();
  2603. PushID(label);
  2604. PushMultiItemsWidths(components, CalcItemWidth());
  2605. size_t type_size = GDataTypeInfo[data_type].Size;
  2606. for (int i = 0; i < components; i++)
  2607. {
  2608. PushID(i);
  2609. if (i > 0)
  2610. SameLine(0, g.Style.ItemInnerSpacing.x);
  2611. value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
  2612. PopID();
  2613. PopItemWidth();
  2614. p_data = (void*)((char*)p_data + type_size);
  2615. }
  2616. PopID();
  2617. const char* label_end = FindRenderedTextEnd(label);
  2618. if (label != label_end)
  2619. {
  2620. SameLine(0.0f, g.Style.ItemInnerSpacing.x);
  2621. TextEx(label, label_end);
  2622. }
  2623. EndGroup();
  2624. return value_changed;
  2625. }
  2626. bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
  2627. {
  2628. flags |= ImGuiInputTextFlags_CharsScientific;
  2629. return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, flags);
  2630. }
  2631. bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
  2632. {
  2633. return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
  2634. }
  2635. bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
  2636. {
  2637. return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
  2638. }
  2639. bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
  2640. {
  2641. return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
  2642. }
  2643. // Prefer using "const char* format" directly, which is more flexible and consistent with other API.
  2644. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
  2645. bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags flags)
  2646. {
  2647. char format[16] = "%f";
  2648. if (decimal_precision >= 0)
  2649. ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
  2650. return InputFloat(label, v, step, step_fast, format, flags);
  2651. }
  2652. bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags flags)
  2653. {
  2654. char format[16] = "%f";
  2655. if (decimal_precision >= 0)
  2656. ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
  2657. return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
  2658. }
  2659. bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags flags)
  2660. {
  2661. char format[16] = "%f";
  2662. if (decimal_precision >= 0)
  2663. ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
  2664. return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
  2665. }
  2666. bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags flags)
  2667. {
  2668. char format[16] = "%f";
  2669. if (decimal_precision >= 0)
  2670. ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
  2671. return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
  2672. }
  2673. #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
  2674. bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
  2675. {
  2676. // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
  2677. const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
  2678. return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, flags);
  2679. }
  2680. bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
  2681. {
  2682. return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
  2683. }
  2684. bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
  2685. {
  2686. return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
  2687. }
  2688. bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
  2689. {
  2690. return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
  2691. }
  2692. bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
  2693. {
  2694. flags |= ImGuiInputTextFlags_CharsScientific;
  2695. return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, flags);
  2696. }
  2697. //-------------------------------------------------------------------------
  2698. // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
  2699. //-------------------------------------------------------------------------
  2700. // - InputText()
  2701. // - InputTextWithHint()
  2702. // - InputTextMultiline()
  2703. // - InputTextEx() [Internal]
  2704. //-------------------------------------------------------------------------
  2705. bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
  2706. {
  2707. IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
  2708. return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
  2709. }
  2710. bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
  2711. {
  2712. return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
  2713. }
  2714. bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
  2715. {
  2716. IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
  2717. return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
  2718. }
  2719. static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
  2720. {
  2721. int line_count = 0;
  2722. const char* s = text_begin;
  2723. while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
  2724. if (c == '\n')
  2725. line_count++;
  2726. s--;
  2727. if (s[0] != '\n' && s[0] != '\r')
  2728. line_count++;
  2729. *out_text_end = s;
  2730. return line_count;
  2731. }
  2732. static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
  2733. {
  2734. ImGuiContext& g = *GImGui;
  2735. ImFont* font = g.Font;
  2736. const float line_height = g.FontSize;
  2737. const float scale = line_height / font->FontSize;
  2738. ImVec2 text_size = ImVec2(0,0);
  2739. float line_width = 0.0f;
  2740. const ImWchar* s = text_begin;
  2741. while (s < text_end)
  2742. {
  2743. unsigned int c = (unsigned int)(*s++);
  2744. if (c == '\n')
  2745. {
  2746. text_size.x = ImMax(text_size.x, line_width);
  2747. text_size.y += line_height;
  2748. line_width = 0.0f;
  2749. if (stop_on_new_line)
  2750. break;
  2751. continue;
  2752. }
  2753. if (c == '\r')
  2754. continue;
  2755. const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
  2756. line_width += char_width;
  2757. }
  2758. if (text_size.x < line_width)
  2759. text_size.x = line_width;
  2760. if (out_offset)
  2761. *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
  2762. if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
  2763. text_size.y += line_height;
  2764. if (remaining)
  2765. *remaining = s;
  2766. return text_size;
  2767. }
  2768. // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
  2769. namespace ImStb
  2770. {
  2771. static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
  2772. static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; }
  2773. static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *GImGui; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
  2774. static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; }
  2775. static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
  2776. static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
  2777. {
  2778. const ImWchar* text = obj->TextW.Data;
  2779. const ImWchar* text_remaining = NULL;
  2780. const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
  2781. r->x0 = 0.0f;
  2782. r->x1 = size.x;
  2783. r->baseline_y_delta = size.y;
  2784. r->ymin = 0.0f;
  2785. r->ymax = size.y;
  2786. r->num_chars = (int)(text_remaining - (text + line_start_idx));
  2787. }
  2788. static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
  2789. static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
  2790. static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
  2791. #ifdef __APPLE__ // FIXME: Move setting to IO structure
  2792. static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
  2793. static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
  2794. #else
  2795. static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
  2796. #endif
  2797. #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
  2798. #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
  2799. static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
  2800. {
  2801. ImWchar* dst = obj->TextW.Data + pos;
  2802. // We maintain our buffer length in both UTF-8 and wchar formats
  2803. obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
  2804. obj->CurLenW -= n;
  2805. // Offset remaining text (FIXME-OPT: Use memmove)
  2806. const ImWchar* src = obj->TextW.Data + pos + n;
  2807. while (ImWchar c = *src++)
  2808. *dst++ = c;
  2809. *dst = '\0';
  2810. }
  2811. static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
  2812. {
  2813. const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
  2814. const int text_len = obj->CurLenW;
  2815. IM_ASSERT(pos <= text_len);
  2816. const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
  2817. if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
  2818. return false;
  2819. // Grow internal buffer if needed
  2820. if (new_text_len + text_len + 1 > obj->TextW.Size)
  2821. {
  2822. if (!is_resizable)
  2823. return false;
  2824. IM_ASSERT(text_len < obj->TextW.Size);
  2825. obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
  2826. }
  2827. ImWchar* text = obj->TextW.Data;
  2828. if (pos != text_len)
  2829. memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
  2830. memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
  2831. obj->CurLenW += new_text_len;
  2832. obj->CurLenA += new_text_len_utf8;
  2833. obj->TextW[obj->CurLenW] = '\0';
  2834. return true;
  2835. }
  2836. // We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
  2837. #define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left
  2838. #define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right
  2839. #define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up
  2840. #define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down
  2841. #define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line
  2842. #define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line
  2843. #define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text
  2844. #define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text
  2845. #define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor
  2846. #define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor
  2847. #define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo
  2848. #define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
  2849. #define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
  2850. #define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
  2851. #define STB_TEXTEDIT_K_SHIFT 0x400000
  2852. #define STB_TEXTEDIT_IMPLEMENTATION
  2853. #include "imstb_textedit.h"
  2854. }
  2855. void ImGuiInputTextState::OnKeyPressed(int key)
  2856. {
  2857. stb_textedit_key(this, &Stb, key);
  2858. CursorFollow = true;
  2859. CursorAnimReset();
  2860. }
  2861. ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
  2862. {
  2863. memset(this, 0, sizeof(*this));
  2864. }
  2865. // Public API to manipulate UTF-8 text
  2866. // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
  2867. // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
  2868. void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
  2869. {
  2870. IM_ASSERT(pos + bytes_count <= BufTextLen);
  2871. char* dst = Buf + pos;
  2872. const char* src = Buf + pos + bytes_count;
  2873. while (char c = *src++)
  2874. *dst++ = c;
  2875. *dst = '\0';
  2876. if (CursorPos + bytes_count >= pos)
  2877. CursorPos -= bytes_count;
  2878. else if (CursorPos >= pos)
  2879. CursorPos = pos;
  2880. SelectionStart = SelectionEnd = CursorPos;
  2881. BufDirty = true;
  2882. BufTextLen -= bytes_count;
  2883. }
  2884. void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
  2885. {
  2886. const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
  2887. const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
  2888. if (new_text_len + BufTextLen >= BufSize)
  2889. {
  2890. if (!is_resizable)
  2891. return;
  2892. // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
  2893. ImGuiContext& g = *GImGui;
  2894. ImGuiInputTextState* edit_state = &g.InputTextState;
  2895. IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
  2896. IM_ASSERT(Buf == edit_state->TextA.Data);
  2897. int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
  2898. edit_state->TextA.reserve(new_buf_size + 1);
  2899. Buf = edit_state->TextA.Data;
  2900. BufSize = edit_state->BufCapacityA = new_buf_size;
  2901. }
  2902. if (BufTextLen != pos)
  2903. memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
  2904. memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
  2905. Buf[BufTextLen + new_text_len] = '\0';
  2906. if (CursorPos >= pos)
  2907. CursorPos += new_text_len;
  2908. SelectionStart = SelectionEnd = CursorPos;
  2909. BufDirty = true;
  2910. BufTextLen += new_text_len;
  2911. }
  2912. // Return false to discard a character.
  2913. static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
  2914. {
  2915. unsigned int c = *p_char;
  2916. // Filter non-printable (NB: isprint is unreliable! see #2467)
  2917. if (c < 0x20)
  2918. {
  2919. bool pass = false;
  2920. pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
  2921. pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
  2922. if (!pass)
  2923. return false;
  2924. }
  2925. // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
  2926. if (c == 127)
  2927. return false;
  2928. // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
  2929. if (c >= 0xE000 && c <= 0xF8FF)
  2930. return false;
  2931. // Filter Unicode ranges we are not handling in this build.
  2932. if (c > IM_UNICODE_CODEPOINT_MAX)
  2933. return false;
  2934. // Generic named filters
  2935. if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
  2936. {
  2937. if (flags & ImGuiInputTextFlags_CharsDecimal)
  2938. if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
  2939. return false;
  2940. if (flags & ImGuiInputTextFlags_CharsScientific)
  2941. if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
  2942. return false;
  2943. if (flags & ImGuiInputTextFlags_CharsHexadecimal)
  2944. if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
  2945. return false;
  2946. if (flags & ImGuiInputTextFlags_CharsUppercase)
  2947. if (c >= 'a' && c <= 'z')
  2948. *p_char = (c += (unsigned int)('A'-'a'));
  2949. if (flags & ImGuiInputTextFlags_CharsNoBlank)
  2950. if (ImCharIsBlankW(c))
  2951. return false;
  2952. }
  2953. // Custom callback filter
  2954. if (flags & ImGuiInputTextFlags_CallbackCharFilter)
  2955. {
  2956. ImGuiInputTextCallbackData callback_data;
  2957. memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
  2958. callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
  2959. callback_data.EventChar = (ImWchar)c;
  2960. callback_data.Flags = flags;
  2961. callback_data.UserData = user_data;
  2962. if (callback(&callback_data) != 0)
  2963. return false;
  2964. *p_char = callback_data.EventChar;
  2965. if (!callback_data.EventChar)
  2966. return false;
  2967. }
  2968. return true;
  2969. }
  2970. // Edit a string of text
  2971. // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
  2972. // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
  2973. // Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
  2974. // - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
  2975. // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
  2976. // (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
  2977. // doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
  2978. bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
  2979. {
  2980. ImGuiWindow* window = GetCurrentWindow();
  2981. if (window->SkipItems)
  2982. return false;
  2983. IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
  2984. IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
  2985. ImGuiContext& g = *GImGui;
  2986. ImGuiIO& io = g.IO;
  2987. const ImGuiStyle& style = g.Style;
  2988. const bool RENDER_SELECTION_WHEN_INACTIVE = false;
  2989. const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
  2990. const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
  2991. const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
  2992. const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
  2993. const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
  2994. if (is_resizable)
  2995. IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
  2996. if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
  2997. BeginGroup();
  2998. const ImGuiID id = window->GetID(label);
  2999. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  3000. const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
  3001. const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
  3002. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
  3003. const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
  3004. ImGuiWindow* draw_window = window;
  3005. ImVec2 inner_size = frame_size;
  3006. if (is_multiline)
  3007. {
  3008. if (!ItemAdd(total_bb, id, &frame_bb))
  3009. {
  3010. ItemSize(total_bb, style.FramePadding.y);
  3011. EndGroup();
  3012. return false;
  3013. }
  3014. if (!BeginChildFrame(id, frame_bb.GetSize()))
  3015. {
  3016. EndChildFrame();
  3017. EndGroup();
  3018. return false;
  3019. }
  3020. draw_window = g.CurrentWindow; // Child window
  3021. draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
  3022. inner_size.x -= draw_window->ScrollbarSizes.x;
  3023. }
  3024. else
  3025. {
  3026. ItemSize(total_bb, style.FramePadding.y);
  3027. if (!ItemAdd(total_bb, id, &frame_bb))
  3028. return false;
  3029. }
  3030. const bool hovered = ItemHoverable(frame_bb, id);
  3031. if (hovered)
  3032. g.MouseCursor = ImGuiMouseCursor_TextInput;
  3033. // NB: we are only allowed to access 'edit_state' if we are the active widget.
  3034. ImGuiInputTextState* state = NULL;
  3035. if (g.InputTextState.ID == id)
  3036. state = &g.InputTextState;
  3037. const bool focus_requested = FocusableItemRegister(window, id);
  3038. const bool focus_requested_by_code = focus_requested && (g.FocusRequestCurrWindow == window && g.FocusRequestCurrCounterAll == window->DC.FocusCounterAll);
  3039. const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
  3040. const bool user_clicked = hovered && io.MouseClicked[0];
  3041. const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
  3042. const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
  3043. const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
  3044. bool clear_active_id = false;
  3045. bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
  3046. const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start);
  3047. const bool init_state = (init_make_active || user_scroll_active);
  3048. if (init_state && g.ActiveId != id)
  3049. {
  3050. // Access state even if we don't own it yet.
  3051. state = &g.InputTextState;
  3052. state->CursorAnimReset();
  3053. // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
  3054. // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
  3055. const int buf_len = (int)strlen(buf);
  3056. state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
  3057. memcpy(state->InitialTextA.Data, buf, buf_len + 1);
  3058. // Start edition
  3059. const char* buf_end = NULL;
  3060. state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
  3061. state->TextA.resize(0);
  3062. state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then)
  3063. state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
  3064. state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
  3065. // Preserve cursor position and undo/redo stack if we come back to same widget
  3066. // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
  3067. const bool recycle_state = (state->ID == id);
  3068. if (recycle_state)
  3069. {
  3070. // Recycle existing cursor/selection/undo stack but clamp position
  3071. // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
  3072. state->CursorClamp();
  3073. }
  3074. else
  3075. {
  3076. state->ID = id;
  3077. state->ScrollX = 0.0f;
  3078. stb_textedit_initialize_state(&state->Stb, !is_multiline);
  3079. if (!is_multiline && focus_requested_by_code)
  3080. select_all = true;
  3081. }
  3082. if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
  3083. state->Stb.insert_mode = 1;
  3084. if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
  3085. select_all = true;
  3086. }
  3087. if (g.ActiveId != id && init_make_active)
  3088. {
  3089. IM_ASSERT(state && state->ID == id);
  3090. SetActiveID(id, window);
  3091. SetFocusID(id, window);
  3092. FocusWindow(window);
  3093. // Declare our inputs
  3094. IM_ASSERT(ImGuiNavInput_COUNT < 32);
  3095. g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
  3096. if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
  3097. g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
  3098. g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
  3099. g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Home) | ((ImU64)1 << ImGuiKey_End);
  3100. if (is_multiline)
  3101. g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_PageUp) | ((ImU64)1 << ImGuiKey_PageDown); // FIXME-NAV: Page up/down actually not supported yet by widget, but claim them ahead.
  3102. if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character.
  3103. g.ActiveIdUsingKeyInputMask |= ((ImU64)1 << ImGuiKey_Tab);
  3104. }
  3105. // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
  3106. if (g.ActiveId == id && state == NULL)
  3107. ClearActiveID();
  3108. // Release focus when we click outside
  3109. if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
  3110. clear_active_id = true;
  3111. // Lock the decision of whether we are going to take the path displaying the cursor or selection
  3112. const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
  3113. bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
  3114. bool value_changed = false;
  3115. bool enter_pressed = false;
  3116. // When read-only we always use the live data passed to the function
  3117. // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
  3118. if (is_readonly && state != NULL && (render_cursor || render_selection))
  3119. {
  3120. const char* buf_end = NULL;
  3121. state->TextW.resize(buf_size + 1);
  3122. state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
  3123. state->CurLenA = (int)(buf_end - buf);
  3124. state->CursorClamp();
  3125. render_selection &= state->HasSelection();
  3126. }
  3127. // Select the buffer to render.
  3128. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
  3129. const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
  3130. // Password pushes a temporary font with only a fallback glyph
  3131. if (is_password && !is_displaying_hint)
  3132. {
  3133. const ImFontGlyph* glyph = g.Font->FindGlyph('*');
  3134. ImFont* password_font = &g.InputTextPasswordFont;
  3135. password_font->FontSize = g.Font->FontSize;
  3136. password_font->Scale = g.Font->Scale;
  3137. password_font->DisplayOffset = g.Font->DisplayOffset;
  3138. password_font->Ascent = g.Font->Ascent;
  3139. password_font->Descent = g.Font->Descent;
  3140. password_font->ContainerAtlas = g.Font->ContainerAtlas;
  3141. password_font->FallbackGlyph = glyph;
  3142. password_font->FallbackAdvanceX = glyph->AdvanceX;
  3143. IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
  3144. PushFont(password_font);
  3145. }
  3146. // Process mouse inputs and character inputs
  3147. int backup_current_text_length = 0;
  3148. if (g.ActiveId == id)
  3149. {
  3150. IM_ASSERT(state != NULL);
  3151. backup_current_text_length = state->CurLenA;
  3152. state->BufCapacityA = buf_size;
  3153. state->UserFlags = flags;
  3154. state->UserCallback = callback;
  3155. state->UserCallbackData = callback_user_data;
  3156. // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
  3157. // Down the line we should have a cleaner library-wide concept of Selected vs Active.
  3158. g.ActiveIdAllowOverlap = !io.MouseDown[0];
  3159. g.WantTextInputNextFrame = 1;
  3160. // Edit in progress
  3161. const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX;
  3162. const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
  3163. const bool is_osx = io.ConfigMacOSXBehaviors;
  3164. if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
  3165. {
  3166. state->SelectAll();
  3167. state->SelectedAllMouseLock = true;
  3168. }
  3169. else if (hovered && is_osx && io.MouseDoubleClicked[0])
  3170. {
  3171. // Double-click select a word only, OS X style (by simulating keystrokes)
  3172. state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
  3173. state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
  3174. }
  3175. else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
  3176. {
  3177. if (hovered)
  3178. {
  3179. stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
  3180. state->CursorAnimReset();
  3181. }
  3182. }
  3183. else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
  3184. {
  3185. stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
  3186. state->CursorAnimReset();
  3187. state->CursorFollow = true;
  3188. }
  3189. if (state->SelectedAllMouseLock && !io.MouseDown[0])
  3190. state->SelectedAllMouseLock = false;
  3191. // It is ill-defined whether the back-end needs to send a \t character when pressing the TAB keys.
  3192. // Win32 and GLFW naturally do it but not SDL.
  3193. const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
  3194. if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
  3195. if (!io.InputQueueCharacters.contains('\t'))
  3196. {
  3197. unsigned int c = '\t'; // Insert TAB
  3198. if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
  3199. state->OnKeyPressed((int)c);
  3200. }
  3201. // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
  3202. // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
  3203. if (io.InputQueueCharacters.Size > 0)
  3204. {
  3205. if (!ignore_char_inputs && !is_readonly && !user_nav_input_start)
  3206. for (int n = 0; n < io.InputQueueCharacters.Size; n++)
  3207. {
  3208. // Insert character if they pass filtering
  3209. unsigned int c = (unsigned int)io.InputQueueCharacters[n];
  3210. if (c == '\t' && io.KeyShift)
  3211. continue;
  3212. if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
  3213. state->OnKeyPressed((int)c);
  3214. }
  3215. // Consume characters
  3216. io.InputQueueCharacters.resize(0);
  3217. }
  3218. }
  3219. // Process other shortcuts/key-presses
  3220. bool cancel_edit = false;
  3221. if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
  3222. {
  3223. IM_ASSERT(state != NULL);
  3224. const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
  3225. const bool is_osx = io.ConfigMacOSXBehaviors;
  3226. const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
  3227. const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
  3228. const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
  3229. const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
  3230. const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
  3231. const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
  3232. const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
  3233. const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
  3234. const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly;
  3235. const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable);
  3236. const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable;
  3237. if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
  3238. else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
  3239. else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
  3240. else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
  3241. else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
  3242. else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
  3243. else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
  3244. else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly)
  3245. {
  3246. if (!state->HasSelection())
  3247. {
  3248. if (is_wordmove_key_down)
  3249. state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
  3250. else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
  3251. state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
  3252. }
  3253. state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
  3254. }
  3255. else if (IsKeyPressedMap(ImGuiKey_Enter) || IsKeyPressedMap(ImGuiKey_KeyPadEnter))
  3256. {
  3257. bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
  3258. if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
  3259. {
  3260. enter_pressed = clear_active_id = true;
  3261. }
  3262. else if (!is_readonly)
  3263. {
  3264. unsigned int c = '\n'; // Insert new line
  3265. if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
  3266. state->OnKeyPressed((int)c);
  3267. }
  3268. }
  3269. else if (IsKeyPressedMap(ImGuiKey_Escape))
  3270. {
  3271. clear_active_id = cancel_edit = true;
  3272. }
  3273. else if (is_undo || is_redo)
  3274. {
  3275. state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
  3276. state->ClearSelection();
  3277. }
  3278. else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
  3279. {
  3280. state->SelectAll();
  3281. state->CursorFollow = true;
  3282. }
  3283. else if (is_cut || is_copy)
  3284. {
  3285. // Cut, Copy
  3286. if (io.SetClipboardTextFn)
  3287. {
  3288. const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
  3289. const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
  3290. const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
  3291. char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
  3292. ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
  3293. SetClipboardText(clipboard_data);
  3294. MemFree(clipboard_data);
  3295. }
  3296. if (is_cut)
  3297. {
  3298. if (!state->HasSelection())
  3299. state->SelectAll();
  3300. state->CursorFollow = true;
  3301. stb_textedit_cut(state, &state->Stb);
  3302. }
  3303. }
  3304. else if (is_paste)
  3305. {
  3306. if (const char* clipboard = GetClipboardText())
  3307. {
  3308. // Filter pasted buffer
  3309. const int clipboard_len = (int)strlen(clipboard);
  3310. ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len+1) * sizeof(ImWchar));
  3311. int clipboard_filtered_len = 0;
  3312. for (const char* s = clipboard; *s; )
  3313. {
  3314. unsigned int c;
  3315. s += ImTextCharFromUtf8(&c, s, NULL);
  3316. if (c == 0)
  3317. break;
  3318. if (!InputTextFilterCharacter(&c, flags, callback, callback_user_data))
  3319. continue;
  3320. clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
  3321. }
  3322. clipboard_filtered[clipboard_filtered_len] = 0;
  3323. if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
  3324. {
  3325. stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
  3326. state->CursorFollow = true;
  3327. }
  3328. MemFree(clipboard_filtered);
  3329. }
  3330. }
  3331. // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
  3332. render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
  3333. }
  3334. // Process callbacks and apply result back to user's buffer.
  3335. if (g.ActiveId == id)
  3336. {
  3337. IM_ASSERT(state != NULL);
  3338. const char* apply_new_text = NULL;
  3339. int apply_new_text_length = 0;
  3340. if (cancel_edit)
  3341. {
  3342. // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
  3343. if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
  3344. {
  3345. apply_new_text = state->InitialTextA.Data;
  3346. apply_new_text_length = state->InitialTextA.Size - 1;
  3347. }
  3348. }
  3349. // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
  3350. // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
  3351. bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
  3352. if (apply_edit_back_to_user_buffer)
  3353. {
  3354. // Apply new value immediately - copy modified buffer back
  3355. // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
  3356. // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
  3357. // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
  3358. if (!is_readonly)
  3359. {
  3360. state->TextAIsValid = true;
  3361. state->TextA.resize(state->TextW.Size * 4 + 1);
  3362. ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
  3363. }
  3364. // User callback
  3365. if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
  3366. {
  3367. IM_ASSERT(callback != NULL);
  3368. // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
  3369. ImGuiInputTextFlags event_flag = 0;
  3370. ImGuiKey event_key = ImGuiKey_COUNT;
  3371. if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
  3372. {
  3373. event_flag = ImGuiInputTextFlags_CallbackCompletion;
  3374. event_key = ImGuiKey_Tab;
  3375. }
  3376. else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
  3377. {
  3378. event_flag = ImGuiInputTextFlags_CallbackHistory;
  3379. event_key = ImGuiKey_UpArrow;
  3380. }
  3381. else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
  3382. {
  3383. event_flag = ImGuiInputTextFlags_CallbackHistory;
  3384. event_key = ImGuiKey_DownArrow;
  3385. }
  3386. else if (flags & ImGuiInputTextFlags_CallbackAlways)
  3387. event_flag = ImGuiInputTextFlags_CallbackAlways;
  3388. if (event_flag)
  3389. {
  3390. ImGuiInputTextCallbackData callback_data;
  3391. memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
  3392. callback_data.EventFlag = event_flag;
  3393. callback_data.Flags = flags;
  3394. callback_data.UserData = callback_user_data;
  3395. callback_data.EventKey = event_key;
  3396. callback_data.Buf = state->TextA.Data;
  3397. callback_data.BufTextLen = state->CurLenA;
  3398. callback_data.BufSize = state->BufCapacityA;
  3399. callback_data.BufDirty = false;
  3400. // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
  3401. ImWchar* text = state->TextW.Data;
  3402. const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
  3403. const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
  3404. const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
  3405. // Call user code
  3406. callback(&callback_data);
  3407. // Read back what user may have modified
  3408. IM_ASSERT(callback_data.Buf == state->TextA.Data); // Invalid to modify those fields
  3409. IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
  3410. IM_ASSERT(callback_data.Flags == flags);
  3411. if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
  3412. if (callback_data.SelectionStart != utf8_selection_start) { state->Stb.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
  3413. if (callback_data.SelectionEnd != utf8_selection_end) { state->Stb.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
  3414. if (callback_data.BufDirty)
  3415. {
  3416. IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
  3417. if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
  3418. state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
  3419. state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
  3420. state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
  3421. state->CursorAnimReset();
  3422. }
  3423. }
  3424. }
  3425. // Will copy result string if modified
  3426. if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
  3427. {
  3428. apply_new_text = state->TextA.Data;
  3429. apply_new_text_length = state->CurLenA;
  3430. }
  3431. }
  3432. // Copy result to user buffer
  3433. if (apply_new_text)
  3434. {
  3435. IM_ASSERT(apply_new_text_length >= 0);
  3436. if (backup_current_text_length != apply_new_text_length && is_resizable)
  3437. {
  3438. ImGuiInputTextCallbackData callback_data;
  3439. callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
  3440. callback_data.Flags = flags;
  3441. callback_data.Buf = buf;
  3442. callback_data.BufTextLen = apply_new_text_length;
  3443. callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
  3444. callback_data.UserData = callback_user_data;
  3445. callback(&callback_data);
  3446. buf = callback_data.Buf;
  3447. buf_size = callback_data.BufSize;
  3448. apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
  3449. IM_ASSERT(apply_new_text_length <= buf_size);
  3450. }
  3451. // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
  3452. ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
  3453. value_changed = true;
  3454. }
  3455. // Clear temporary user storage
  3456. state->UserFlags = 0;
  3457. state->UserCallback = NULL;
  3458. state->UserCallbackData = NULL;
  3459. }
  3460. // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
  3461. if (clear_active_id && g.ActiveId == id)
  3462. ClearActiveID();
  3463. // Render frame
  3464. if (!is_multiline)
  3465. {
  3466. RenderNavHighlight(frame_bb, id);
  3467. RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
  3468. }
  3469. const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
  3470. ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
  3471. ImVec2 text_size(0.0f, 0.0f);
  3472. // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
  3473. // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
  3474. // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
  3475. const int buf_display_max_length = 2 * 1024 * 1024;
  3476. const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
  3477. const char* buf_display_end = NULL; // We have specialized paths below for setting the length
  3478. if (is_displaying_hint)
  3479. {
  3480. buf_display = hint;
  3481. buf_display_end = hint + strlen(hint);
  3482. }
  3483. // Render text. We currently only render selection when the widget is active or while scrolling.
  3484. // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
  3485. if (render_cursor || render_selection)
  3486. {
  3487. IM_ASSERT(state != NULL);
  3488. if (!is_displaying_hint)
  3489. buf_display_end = buf_display + state->CurLenA;
  3490. // Render text (with cursor and selection)
  3491. // This is going to be messy. We need to:
  3492. // - Display the text (this alone can be more easily clipped)
  3493. // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
  3494. // - Measure text height (for scrollbar)
  3495. // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
  3496. // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
  3497. const ImWchar* text_begin = state->TextW.Data;
  3498. ImVec2 cursor_offset, select_start_offset;
  3499. {
  3500. // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
  3501. const ImWchar* searches_input_ptr[2] = { NULL, NULL };
  3502. int searches_result_line_no[2] = { -1000, -1000 };
  3503. int searches_remaining = 0;
  3504. if (render_cursor)
  3505. {
  3506. searches_input_ptr[0] = text_begin + state->Stb.cursor;
  3507. searches_result_line_no[0] = -1;
  3508. searches_remaining++;
  3509. }
  3510. if (render_selection)
  3511. {
  3512. searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
  3513. searches_result_line_no[1] = -1;
  3514. searches_remaining++;
  3515. }
  3516. // Iterate all lines to find our line numbers
  3517. // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
  3518. searches_remaining += is_multiline ? 1 : 0;
  3519. int line_count = 0;
  3520. //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bit
  3521. for (const ImWchar* s = text_begin; *s != 0; s++)
  3522. if (*s == '\n')
  3523. {
  3524. line_count++;
  3525. if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
  3526. if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
  3527. }
  3528. line_count++;
  3529. if (searches_result_line_no[0] == -1)
  3530. searches_result_line_no[0] = line_count;
  3531. if (searches_result_line_no[1] == -1)
  3532. searches_result_line_no[1] = line_count;
  3533. // Calculate 2d position by finding the beginning of the line and measuring distance
  3534. cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
  3535. cursor_offset.y = searches_result_line_no[0] * g.FontSize;
  3536. if (searches_result_line_no[1] >= 0)
  3537. {
  3538. select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
  3539. select_start_offset.y = searches_result_line_no[1] * g.FontSize;
  3540. }
  3541. // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
  3542. if (is_multiline)
  3543. text_size = ImVec2(inner_size.x, line_count * g.FontSize);
  3544. }
  3545. // Scroll
  3546. if (render_cursor && state->CursorFollow)
  3547. {
  3548. // Horizontal scroll in chunks of quarter width
  3549. if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
  3550. {
  3551. const float scroll_increment_x = inner_size.x * 0.25f;
  3552. if (cursor_offset.x < state->ScrollX)
  3553. state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
  3554. else if (cursor_offset.x - inner_size.x >= state->ScrollX)
  3555. state->ScrollX = IM_FLOOR(cursor_offset.x - inner_size.x + scroll_increment_x);
  3556. }
  3557. else
  3558. {
  3559. state->ScrollX = 0.0f;
  3560. }
  3561. // Vertical scroll
  3562. if (is_multiline)
  3563. {
  3564. float scroll_y = draw_window->Scroll.y;
  3565. if (cursor_offset.y - g.FontSize < scroll_y)
  3566. scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
  3567. else if (cursor_offset.y - inner_size.y >= scroll_y)
  3568. scroll_y = cursor_offset.y - inner_size.y;
  3569. draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
  3570. draw_window->Scroll.y = scroll_y;
  3571. }
  3572. state->CursorFollow = false;
  3573. }
  3574. // Draw selection
  3575. const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
  3576. if (render_selection)
  3577. {
  3578. const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
  3579. const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
  3580. ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
  3581. float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
  3582. float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
  3583. ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
  3584. for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
  3585. {
  3586. if (rect_pos.y > clip_rect.w + g.FontSize)
  3587. break;
  3588. if (rect_pos.y < clip_rect.y)
  3589. {
  3590. //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit
  3591. //p = p ? p + 1 : text_selected_end;
  3592. while (p < text_selected_end)
  3593. if (*p++ == '\n')
  3594. break;
  3595. }
  3596. else
  3597. {
  3598. ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
  3599. if (rect_size.x <= 0.0f) rect_size.x = IM_FLOOR(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
  3600. ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
  3601. rect.ClipWith(clip_rect);
  3602. if (rect.Overlaps(clip_rect))
  3603. draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
  3604. }
  3605. rect_pos.x = draw_pos.x - draw_scroll.x;
  3606. rect_pos.y += g.FontSize;
  3607. }
  3608. }
  3609. // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
  3610. if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
  3611. {
  3612. ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
  3613. draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
  3614. }
  3615. // Draw blinking cursor
  3616. if (render_cursor)
  3617. {
  3618. state->CursorAnim += io.DeltaTime;
  3619. bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
  3620. ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll;
  3621. ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f);
  3622. if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
  3623. draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
  3624. // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
  3625. if (!is_readonly)
  3626. g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
  3627. }
  3628. }
  3629. else
  3630. {
  3631. // Render text only (no selection, no cursor)
  3632. if (is_multiline)
  3633. text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
  3634. else if (!is_displaying_hint && g.ActiveId == id)
  3635. buf_display_end = buf_display + state->CurLenA;
  3636. else if (!is_displaying_hint)
  3637. buf_display_end = buf_display + strlen(buf_display);
  3638. if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
  3639. {
  3640. ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
  3641. draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
  3642. }
  3643. }
  3644. if (is_multiline)
  3645. {
  3646. Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
  3647. EndChildFrame();
  3648. EndGroup();
  3649. }
  3650. if (is_password && !is_displaying_hint)
  3651. PopFont();
  3652. // Log as text
  3653. if (g.LogEnabled && !(is_password && !is_displaying_hint))
  3654. LogRenderedText(&draw_pos, buf_display, buf_display_end);
  3655. if (label_size.x > 0)
  3656. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  3657. if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited))
  3658. MarkItemEdited(id);
  3659. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
  3660. if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
  3661. return enter_pressed;
  3662. else
  3663. return value_changed;
  3664. }
  3665. //-------------------------------------------------------------------------
  3666. // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
  3667. //-------------------------------------------------------------------------
  3668. // - ColorEdit3()
  3669. // - ColorEdit4()
  3670. // - ColorPicker3()
  3671. // - RenderColorRectWithAlphaCheckerboard() [Internal]
  3672. // - ColorPicker4()
  3673. // - ColorButton()
  3674. // - SetColorEditOptions()
  3675. // - ColorTooltip() [Internal]
  3676. // - ColorEditOptionsPopup() [Internal]
  3677. // - ColorPickerOptionsPopup() [Internal]
  3678. //-------------------------------------------------------------------------
  3679. bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
  3680. {
  3681. return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
  3682. }
  3683. // Edit colors components (each component in 0.0f..1.0f range).
  3684. // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
  3685. // With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
  3686. bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
  3687. {
  3688. ImGuiWindow* window = GetCurrentWindow();
  3689. if (window->SkipItems)
  3690. return false;
  3691. ImGuiContext& g = *GImGui;
  3692. const ImGuiStyle& style = g.Style;
  3693. const float square_sz = GetFrameHeight();
  3694. const float w_full = CalcItemWidth();
  3695. const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
  3696. const float w_inputs = w_full - w_button;
  3697. const char* label_display_end = FindRenderedTextEnd(label);
  3698. g.NextItemData.ClearFlags();
  3699. BeginGroup();
  3700. PushID(label);
  3701. // If we're not showing any slider there's no point in doing any HSV conversions
  3702. const ImGuiColorEditFlags flags_untouched = flags;
  3703. if (flags & ImGuiColorEditFlags_NoInputs)
  3704. flags = (flags & (~ImGuiColorEditFlags__DisplayMask)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
  3705. // Context menu: display and modify options (before defaults are applied)
  3706. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3707. ColorEditOptionsPopup(col, flags);
  3708. // Read stored options
  3709. if (!(flags & ImGuiColorEditFlags__DisplayMask))
  3710. flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DisplayMask);
  3711. if (!(flags & ImGuiColorEditFlags__DataTypeMask))
  3712. flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
  3713. if (!(flags & ImGuiColorEditFlags__PickerMask))
  3714. flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
  3715. if (!(flags & ImGuiColorEditFlags__InputMask))
  3716. flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputMask);
  3717. flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask));
  3718. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check that only 1 is selected
  3719. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected
  3720. const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
  3721. const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
  3722. const int components = alpha ? 4 : 3;
  3723. // Convert to the formats we need
  3724. float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
  3725. if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
  3726. ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
  3727. else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
  3728. {
  3729. // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
  3730. ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
  3731. if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
  3732. {
  3733. if (f[1] == 0)
  3734. f[0] = g.ColorEditLastHue;
  3735. if (f[2] == 0)
  3736. f[1] = g.ColorEditLastSat;
  3737. }
  3738. }
  3739. int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
  3740. bool value_changed = false;
  3741. bool value_changed_as_float = false;
  3742. const ImVec2 pos = window->DC.CursorPos;
  3743. const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
  3744. window->DC.CursorPos.x = pos.x + inputs_offset_x;
  3745. if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
  3746. {
  3747. // RGB/HSV 0..255 Sliders
  3748. const float w_item_one = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
  3749. const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
  3750. const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
  3751. static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
  3752. static const char* fmt_table_int[3][4] =
  3753. {
  3754. { "%3d", "%3d", "%3d", "%3d" }, // Short display
  3755. { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
  3756. { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
  3757. };
  3758. static const char* fmt_table_float[3][4] =
  3759. {
  3760. { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
  3761. { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
  3762. { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
  3763. };
  3764. const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
  3765. for (int n = 0; n < components; n++)
  3766. {
  3767. if (n > 0)
  3768. SameLine(0, style.ItemInnerSpacing.x);
  3769. SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last);
  3770. // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
  3771. if (flags & ImGuiColorEditFlags_Float)
  3772. {
  3773. value_changed |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
  3774. value_changed_as_float |= value_changed;
  3775. }
  3776. else
  3777. {
  3778. value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
  3779. }
  3780. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3781. OpenPopupOnItemClick("context");
  3782. }
  3783. }
  3784. else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
  3785. {
  3786. // RGB Hexadecimal Input
  3787. char buf[64];
  3788. if (alpha)
  3789. ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
  3790. else
  3791. ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
  3792. SetNextItemWidth(w_inputs);
  3793. if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
  3794. {
  3795. value_changed = true;
  3796. char* p = buf;
  3797. while (*p == '#' || ImCharIsBlankA(*p))
  3798. p++;
  3799. i[0] = i[1] = i[2] = i[3] = 0;
  3800. if (alpha)
  3801. sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
  3802. else
  3803. sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
  3804. }
  3805. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3806. OpenPopupOnItemClick("context");
  3807. }
  3808. ImGuiWindow* picker_active_window = NULL;
  3809. if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
  3810. {
  3811. const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
  3812. window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
  3813. const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
  3814. if (ColorButton("##ColorButton", col_v4, flags))
  3815. {
  3816. if (!(flags & ImGuiColorEditFlags_NoPicker))
  3817. {
  3818. // Store current color and open a picker
  3819. g.ColorPickerRef = col_v4;
  3820. OpenPopup("picker");
  3821. SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
  3822. }
  3823. }
  3824. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3825. OpenPopupOnItemClick("context");
  3826. if (BeginPopup("picker"))
  3827. {
  3828. picker_active_window = g.CurrentWindow;
  3829. if (label != label_display_end)
  3830. {
  3831. TextEx(label, label_display_end);
  3832. Spacing();
  3833. }
  3834. ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
  3835. ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
  3836. SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
  3837. value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
  3838. EndPopup();
  3839. }
  3840. }
  3841. if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
  3842. {
  3843. const float text_offset_x = (flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x;
  3844. window->DC.CursorPos = ImVec2(pos.x + text_offset_x, pos.y + style.FramePadding.y);
  3845. TextEx(label, label_display_end);
  3846. }
  3847. // Convert back
  3848. if (value_changed && picker_active_window == NULL)
  3849. {
  3850. if (!value_changed_as_float)
  3851. for (int n = 0; n < 4; n++)
  3852. f[n] = i[n] / 255.0f;
  3853. if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
  3854. {
  3855. g.ColorEditLastHue = f[0];
  3856. g.ColorEditLastSat = f[1];
  3857. ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
  3858. memcpy(g.ColorEditLastColor, f, sizeof(float) * 3);
  3859. }
  3860. if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
  3861. ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
  3862. col[0] = f[0];
  3863. col[1] = f[1];
  3864. col[2] = f[2];
  3865. if (alpha)
  3866. col[3] = f[3];
  3867. }
  3868. PopID();
  3869. EndGroup();
  3870. // Drag and Drop Target
  3871. // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
  3872. if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
  3873. {
  3874. bool accepted_drag_drop = false;
  3875. if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
  3876. {
  3877. memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
  3878. value_changed = accepted_drag_drop = true;
  3879. }
  3880. if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
  3881. {
  3882. memcpy((float*)col, payload->Data, sizeof(float) * components);
  3883. value_changed = accepted_drag_drop = true;
  3884. }
  3885. // Drag-drop payloads are always RGB
  3886. if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
  3887. ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
  3888. EndDragDropTarget();
  3889. }
  3890. // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
  3891. if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
  3892. window->DC.LastItemId = g.ActiveId;
  3893. if (value_changed)
  3894. MarkItemEdited(window->DC.LastItemId);
  3895. return value_changed;
  3896. }
  3897. bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
  3898. {
  3899. float col4[4] = { col[0], col[1], col[2], 1.0f };
  3900. if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
  3901. return false;
  3902. col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
  3903. return true;
  3904. }
  3905. static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
  3906. {
  3907. float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
  3908. int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
  3909. int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
  3910. int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
  3911. return IM_COL32(r, g, b, 0xFF);
  3912. }
  3913. // Helper for ColorPicker4()
  3914. // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
  3915. // I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
  3916. void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
  3917. {
  3918. ImGuiWindow* window = GetCurrentWindow();
  3919. if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
  3920. {
  3921. ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
  3922. ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
  3923. window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
  3924. int yi = 0;
  3925. for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
  3926. {
  3927. float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
  3928. if (y2 <= y1)
  3929. continue;
  3930. for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
  3931. {
  3932. float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
  3933. if (x2 <= x1)
  3934. continue;
  3935. int rounding_corners_flags_cell = 0;
  3936. if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
  3937. if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
  3938. rounding_corners_flags_cell &= rounding_corners_flags;
  3939. window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
  3940. }
  3941. }
  3942. }
  3943. else
  3944. {
  3945. window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
  3946. }
  3947. }
  3948. // Helper for ColorPicker4()
  3949. static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
  3950. {
  3951. ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
  3952. ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8));
  3953. ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
  3954. ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8));
  3955. ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8));
  3956. }
  3957. // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
  3958. // (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
  3959. // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
  3960. // FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
  3961. bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
  3962. {
  3963. ImGuiContext& g = *GImGui;
  3964. ImGuiWindow* window = GetCurrentWindow();
  3965. if (window->SkipItems)
  3966. return false;
  3967. ImDrawList* draw_list = window->DrawList;
  3968. ImGuiStyle& style = g.Style;
  3969. ImGuiIO& io = g.IO;
  3970. const float width = CalcItemWidth();
  3971. g.NextItemData.ClearFlags();
  3972. PushID(label);
  3973. BeginGroup();
  3974. if (!(flags & ImGuiColorEditFlags_NoSidePreview))
  3975. flags |= ImGuiColorEditFlags_NoSmallPreview;
  3976. // Context menu: display and store options.
  3977. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3978. ColorPickerOptionsPopup(col, flags);
  3979. // Read stored options
  3980. if (!(flags & ImGuiColorEditFlags__PickerMask))
  3981. flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
  3982. if (!(flags & ImGuiColorEditFlags__InputMask))
  3983. flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__InputMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__InputMask;
  3984. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check that only 1 is selected
  3985. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check that only 1 is selected
  3986. if (!(flags & ImGuiColorEditFlags_NoOptions))
  3987. flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
  3988. // Setup
  3989. int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
  3990. bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
  3991. ImVec2 picker_pos = window->DC.CursorPos;
  3992. float square_sz = GetFrameHeight();
  3993. float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
  3994. float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
  3995. float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
  3996. float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
  3997. float bars_triangles_half_sz = IM_FLOOR(bars_width * 0.20f);
  3998. float backup_initial_col[4];
  3999. memcpy(backup_initial_col, col, components * sizeof(float));
  4000. float wheel_thickness = sv_picker_size * 0.08f;
  4001. float wheel_r_outer = sv_picker_size * 0.50f;
  4002. float wheel_r_inner = wheel_r_outer - wheel_thickness;
  4003. ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
  4004. // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
  4005. float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
  4006. ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
  4007. ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
  4008. ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
  4009. float H = col[0], S = col[1], V = col[2];
  4010. float R = col[0], G = col[1], B = col[2];
  4011. if (flags & ImGuiColorEditFlags_InputRGB)
  4012. {
  4013. // Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
  4014. ColorConvertRGBtoHSV(R, G, B, H, S, V);
  4015. if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
  4016. {
  4017. if (S == 0)
  4018. H = g.ColorEditLastHue;
  4019. if (V == 0)
  4020. S = g.ColorEditLastSat;
  4021. }
  4022. }
  4023. else if (flags & ImGuiColorEditFlags_InputHSV)
  4024. {
  4025. ColorConvertHSVtoRGB(H, S, V, R, G, B);
  4026. }
  4027. bool value_changed = false, value_changed_h = false, value_changed_sv = false;
  4028. PushItemFlag(ImGuiItemFlags_NoNav, true);
  4029. if (flags & ImGuiColorEditFlags_PickerHueWheel)
  4030. {
  4031. // Hue wheel + SV triangle logic
  4032. InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
  4033. if (IsItemActive())
  4034. {
  4035. ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
  4036. ImVec2 current_off = g.IO.MousePos - wheel_center;
  4037. float initial_dist2 = ImLengthSqr(initial_off);
  4038. if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
  4039. {
  4040. // Interactive with Hue wheel
  4041. H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
  4042. if (H < 0.0f)
  4043. H += 1.0f;
  4044. value_changed = value_changed_h = true;
  4045. }
  4046. float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
  4047. float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
  4048. if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
  4049. {
  4050. // Interacting with SV triangle
  4051. ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
  4052. if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
  4053. current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
  4054. float uu, vv, ww;
  4055. ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
  4056. V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
  4057. S = ImClamp(uu / V, 0.0001f, 1.0f);
  4058. value_changed = value_changed_sv = true;
  4059. }
  4060. }
  4061. if (!(flags & ImGuiColorEditFlags_NoOptions))
  4062. OpenPopupOnItemClick("context");
  4063. }
  4064. else if (flags & ImGuiColorEditFlags_PickerHueBar)
  4065. {
  4066. // SV rectangle logic
  4067. InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
  4068. if (IsItemActive())
  4069. {
  4070. S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
  4071. V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
  4072. value_changed = value_changed_sv = true;
  4073. }
  4074. if (!(flags & ImGuiColorEditFlags_NoOptions))
  4075. OpenPopupOnItemClick("context");
  4076. // Hue bar logic
  4077. SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
  4078. InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
  4079. if (IsItemActive())
  4080. {
  4081. H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
  4082. value_changed = value_changed_h = true;
  4083. }
  4084. }
  4085. // Alpha bar logic
  4086. if (alpha_bar)
  4087. {
  4088. SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
  4089. InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
  4090. if (IsItemActive())
  4091. {
  4092. col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
  4093. value_changed = true;
  4094. }
  4095. }
  4096. PopItemFlag(); // ImGuiItemFlags_NoNav
  4097. if (!(flags & ImGuiColorEditFlags_NoSidePreview))
  4098. {
  4099. SameLine(0, style.ItemInnerSpacing.x);
  4100. BeginGroup();
  4101. }
  4102. if (!(flags & ImGuiColorEditFlags_NoLabel))
  4103. {
  4104. const char* label_display_end = FindRenderedTextEnd(label);
  4105. if (label != label_display_end)
  4106. {
  4107. if ((flags & ImGuiColorEditFlags_NoSidePreview))
  4108. SameLine(0, style.ItemInnerSpacing.x);
  4109. TextEx(label, label_display_end);
  4110. }
  4111. }
  4112. if (!(flags & ImGuiColorEditFlags_NoSidePreview))
  4113. {
  4114. PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
  4115. ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
  4116. if ((flags & ImGuiColorEditFlags_NoLabel))
  4117. Text("Current");
  4118. ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
  4119. ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
  4120. if (ref_col != NULL)
  4121. {
  4122. Text("Original");
  4123. ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
  4124. if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
  4125. {
  4126. memcpy(col, ref_col, components * sizeof(float));
  4127. value_changed = true;
  4128. }
  4129. }
  4130. PopItemFlag();
  4131. EndGroup();
  4132. }
  4133. // Convert back color to RGB
  4134. if (value_changed_h || value_changed_sv)
  4135. {
  4136. if (flags & ImGuiColorEditFlags_InputRGB)
  4137. {
  4138. ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
  4139. g.ColorEditLastHue = H;
  4140. g.ColorEditLastSat = S;
  4141. memcpy(g.ColorEditLastColor, col, sizeof(float) * 3);
  4142. }
  4143. else if (flags & ImGuiColorEditFlags_InputHSV)
  4144. {
  4145. col[0] = H;
  4146. col[1] = S;
  4147. col[2] = V;
  4148. }
  4149. }
  4150. // R,G,B and H,S,V slider color editor
  4151. bool value_changed_fix_hue_wrap = false;
  4152. if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
  4153. {
  4154. PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
  4155. ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
  4156. ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
  4157. if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
  4158. if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
  4159. {
  4160. // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
  4161. // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
  4162. value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
  4163. value_changed = true;
  4164. }
  4165. if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
  4166. value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
  4167. if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags__DisplayMask) == 0)
  4168. value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
  4169. PopItemWidth();
  4170. }
  4171. // Try to cancel hue wrap (after ColorEdit4 call), if any
  4172. if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
  4173. {
  4174. float new_H, new_S, new_V;
  4175. ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
  4176. if (new_H <= 0 && H > 0)
  4177. {
  4178. if (new_V <= 0 && V != new_V)
  4179. ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
  4180. else if (new_S <= 0)
  4181. ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
  4182. }
  4183. }
  4184. if (value_changed)
  4185. {
  4186. if (flags & ImGuiColorEditFlags_InputRGB)
  4187. {
  4188. R = col[0];
  4189. G = col[1];
  4190. B = col[2];
  4191. ColorConvertRGBtoHSV(R, G, B, H, S, V);
  4192. if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately.
  4193. {
  4194. if (S == 0)
  4195. H = g.ColorEditLastHue;
  4196. if (V == 0)
  4197. S = g.ColorEditLastSat;
  4198. }
  4199. }
  4200. else if (flags & ImGuiColorEditFlags_InputHSV)
  4201. {
  4202. H = col[0];
  4203. S = col[1];
  4204. V = col[2];
  4205. ColorConvertHSVtoRGB(H, S, V, R, G, B);
  4206. }
  4207. }
  4208. const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
  4209. const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
  4210. const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
  4211. const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
  4212. const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };
  4213. ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
  4214. ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
  4215. ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
  4216. ImVec2 sv_cursor_pos;
  4217. if (flags & ImGuiColorEditFlags_PickerHueWheel)
  4218. {
  4219. // Render Hue Wheel
  4220. const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
  4221. const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
  4222. for (int n = 0; n < 6; n++)
  4223. {
  4224. const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
  4225. const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
  4226. const int vert_start_idx = draw_list->VtxBuffer.Size;
  4227. draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
  4228. draw_list->PathStroke(col_white, false, wheel_thickness);
  4229. const int vert_end_idx = draw_list->VtxBuffer.Size;
  4230. // Paint colors over existing vertices
  4231. ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
  4232. ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
  4233. ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n+1]);
  4234. }
  4235. // Render Cursor + preview on Hue Wheel
  4236. float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
  4237. float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
  4238. ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
  4239. float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
  4240. int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
  4241. draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
  4242. draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, col_midgrey, hue_cursor_segments);
  4243. draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
  4244. // Render SV triangle (rotated according to hue)
  4245. ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
  4246. ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
  4247. ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
  4248. ImVec2 uv_white = GetFontTexUvWhitePixel();
  4249. draw_list->PrimReserve(6, 6);
  4250. draw_list->PrimVtx(tra, uv_white, hue_color32);
  4251. draw_list->PrimVtx(trb, uv_white, hue_color32);
  4252. draw_list->PrimVtx(trc, uv_white, col_white);
  4253. draw_list->PrimVtx(tra, uv_white, 0);
  4254. draw_list->PrimVtx(trb, uv_white, col_black);
  4255. draw_list->PrimVtx(trc, uv_white, 0);
  4256. draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
  4257. sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
  4258. }
  4259. else if (flags & ImGuiColorEditFlags_PickerHueBar)
  4260. {
  4261. // Render SV Square
  4262. draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
  4263. draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
  4264. RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
  4265. sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
  4266. sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
  4267. // Render Hue Bar
  4268. for (int i = 0; i < 6; ++i)
  4269. draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);
  4270. float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
  4271. RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
  4272. RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
  4273. }
  4274. // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
  4275. float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
  4276. draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, 12);
  4277. draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, col_midgrey, 12);
  4278. draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12);
  4279. // Render alpha bar
  4280. if (alpha_bar)
  4281. {
  4282. float alpha = ImSaturate(col[3]);
  4283. ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
  4284. RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
  4285. draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);
  4286. float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
  4287. RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
  4288. RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);
  4289. }
  4290. EndGroup();
  4291. if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
  4292. value_changed = false;
  4293. if (value_changed)
  4294. MarkItemEdited(window->DC.LastItemId);
  4295. PopID();
  4296. return value_changed;
  4297. }
  4298. // A little colored square. Return true when clicked.
  4299. // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
  4300. // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
  4301. // Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
  4302. bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
  4303. {
  4304. ImGuiWindow* window = GetCurrentWindow();
  4305. if (window->SkipItems)
  4306. return false;
  4307. ImGuiContext& g = *GImGui;
  4308. const ImGuiID id = window->GetID(desc_id);
  4309. float default_size = GetFrameHeight();
  4310. if (size.x == 0.0f)
  4311. size.x = default_size;
  4312. if (size.y == 0.0f)
  4313. size.y = default_size;
  4314. const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
  4315. ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
  4316. if (!ItemAdd(bb, id))
  4317. return false;
  4318. bool hovered, held;
  4319. bool pressed = ButtonBehavior(bb, id, &hovered, &held);
  4320. if (flags & ImGuiColorEditFlags_NoAlpha)
  4321. flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
  4322. ImVec4 col_rgb = col;
  4323. if (flags & ImGuiColorEditFlags_InputHSV)
  4324. ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
  4325. ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
  4326. float grid_step = ImMin(size.x, size.y) / 2.99f;
  4327. float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
  4328. ImRect bb_inner = bb;
  4329. float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
  4330. bb_inner.Expand(off);
  4331. if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
  4332. {
  4333. float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
  4334. RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
  4335. window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
  4336. }
  4337. else
  4338. {
  4339. // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
  4340. ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
  4341. if (col_source.w < 1.0f)
  4342. RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
  4343. else
  4344. window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
  4345. }
  4346. RenderNavHighlight(bb, id);
  4347. if (g.Style.FrameBorderSize > 0.0f)
  4348. RenderFrameBorder(bb.Min, bb.Max, rounding);
  4349. else
  4350. window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
  4351. // Drag and Drop Source
  4352. // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
  4353. if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
  4354. {
  4355. if (flags & ImGuiColorEditFlags_NoAlpha)
  4356. SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
  4357. else
  4358. SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
  4359. ColorButton(desc_id, col, flags);
  4360. SameLine();
  4361. TextEx("Color");
  4362. EndDragDropSource();
  4363. }
  4364. // Tooltip
  4365. if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
  4366. ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
  4367. return pressed;
  4368. }
  4369. // Initialize/override default color options
  4370. void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
  4371. {
  4372. ImGuiContext& g = *GImGui;
  4373. if ((flags & ImGuiColorEditFlags__DisplayMask) == 0)
  4374. flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DisplayMask;
  4375. if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
  4376. flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
  4377. if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
  4378. flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
  4379. if ((flags & ImGuiColorEditFlags__InputMask) == 0)
  4380. flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputMask;
  4381. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DisplayMask)); // Check only 1 option is selected
  4382. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__DataTypeMask)); // Check only 1 option is selected
  4383. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__PickerMask)); // Check only 1 option is selected
  4384. IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags__InputMask)); // Check only 1 option is selected
  4385. g.ColorEditOptions = flags;
  4386. }
  4387. // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
  4388. void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
  4389. {
  4390. ImGuiContext& g = *GImGui;
  4391. BeginTooltipEx(0, true);
  4392. const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
  4393. if (text_end > text)
  4394. {
  4395. TextEx(text, text_end);
  4396. Separator();
  4397. }
  4398. ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
  4399. ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
  4400. int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
  4401. ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
  4402. SameLine();
  4403. if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags__InputMask))
  4404. {
  4405. if (flags & ImGuiColorEditFlags_NoAlpha)
  4406. Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
  4407. else
  4408. Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
  4409. }
  4410. else if (flags & ImGuiColorEditFlags_InputHSV)
  4411. {
  4412. if (flags & ImGuiColorEditFlags_NoAlpha)
  4413. Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
  4414. else
  4415. Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
  4416. }
  4417. EndTooltip();
  4418. }
  4419. void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
  4420. {
  4421. bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__DisplayMask);
  4422. bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
  4423. if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
  4424. return;
  4425. ImGuiContext& g = *GImGui;
  4426. ImGuiColorEditFlags opts = g.ColorEditOptions;
  4427. if (allow_opt_inputs)
  4428. {
  4429. if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayRGB;
  4430. if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHSV;
  4431. if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags__DisplayMask) | ImGuiColorEditFlags_DisplayHex;
  4432. }
  4433. if (allow_opt_datatype)
  4434. {
  4435. if (allow_opt_inputs) Separator();
  4436. if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
  4437. if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
  4438. }
  4439. if (allow_opt_inputs || allow_opt_datatype)
  4440. Separator();
  4441. if (Button("Copy as..", ImVec2(-1,0)))
  4442. OpenPopup("Copy");
  4443. if (BeginPopup("Copy"))
  4444. {
  4445. int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
  4446. char buf[64];
  4447. ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
  4448. if (Selectable(buf))
  4449. SetClipboardText(buf);
  4450. ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
  4451. if (Selectable(buf))
  4452. SetClipboardText(buf);
  4453. if (flags & ImGuiColorEditFlags_NoAlpha)
  4454. ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
  4455. else
  4456. ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
  4457. if (Selectable(buf))
  4458. SetClipboardText(buf);
  4459. EndPopup();
  4460. }
  4461. g.ColorEditOptions = opts;
  4462. EndPopup();
  4463. }
  4464. void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
  4465. {
  4466. bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
  4467. bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
  4468. if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
  4469. return;
  4470. ImGuiContext& g = *GImGui;
  4471. if (allow_opt_picker)
  4472. {
  4473. ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
  4474. PushItemWidth(picker_size.x);
  4475. for (int picker_type = 0; picker_type < 2; picker_type++)
  4476. {
  4477. // Draw small/thumbnail version of each picker type (over an invisible button for selection)
  4478. if (picker_type > 0) Separator();
  4479. PushID(picker_type);
  4480. ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
  4481. if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
  4482. if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
  4483. ImVec2 backup_pos = GetCursorScreenPos();
  4484. if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
  4485. g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
  4486. SetCursorScreenPos(backup_pos);
  4487. ImVec4 dummy_ref_col;
  4488. memcpy(&dummy_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
  4489. ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
  4490. PopID();
  4491. }
  4492. PopItemWidth();
  4493. }
  4494. if (allow_opt_alpha_bar)
  4495. {
  4496. if (allow_opt_picker) Separator();
  4497. CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
  4498. }
  4499. EndPopup();
  4500. }
  4501. //-------------------------------------------------------------------------
  4502. // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
  4503. //-------------------------------------------------------------------------
  4504. // - TreeNode()
  4505. // - TreeNodeV()
  4506. // - TreeNodeEx()
  4507. // - TreeNodeExV()
  4508. // - TreeNodeBehavior() [Internal]
  4509. // - TreePush()
  4510. // - TreePop()
  4511. // - GetTreeNodeToLabelSpacing()
  4512. // - SetNextItemOpen()
  4513. // - CollapsingHeader()
  4514. //-------------------------------------------------------------------------
  4515. bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
  4516. {
  4517. va_list args;
  4518. va_start(args, fmt);
  4519. bool is_open = TreeNodeExV(str_id, 0, fmt, args);
  4520. va_end(args);
  4521. return is_open;
  4522. }
  4523. bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
  4524. {
  4525. va_list args;
  4526. va_start(args, fmt);
  4527. bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
  4528. va_end(args);
  4529. return is_open;
  4530. }
  4531. bool ImGui::TreeNode(const char* label)
  4532. {
  4533. ImGuiWindow* window = GetCurrentWindow();
  4534. if (window->SkipItems)
  4535. return false;
  4536. return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
  4537. }
  4538. bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
  4539. {
  4540. return TreeNodeExV(str_id, 0, fmt, args);
  4541. }
  4542. bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
  4543. {
  4544. return TreeNodeExV(ptr_id, 0, fmt, args);
  4545. }
  4546. bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
  4547. {
  4548. ImGuiWindow* window = GetCurrentWindow();
  4549. if (window->SkipItems)
  4550. return false;
  4551. return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
  4552. }
  4553. bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
  4554. {
  4555. va_list args;
  4556. va_start(args, fmt);
  4557. bool is_open = TreeNodeExV(str_id, flags, fmt, args);
  4558. va_end(args);
  4559. return is_open;
  4560. }
  4561. bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
  4562. {
  4563. va_list args;
  4564. va_start(args, fmt);
  4565. bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
  4566. va_end(args);
  4567. return is_open;
  4568. }
  4569. bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
  4570. {
  4571. ImGuiWindow* window = GetCurrentWindow();
  4572. if (window->SkipItems)
  4573. return false;
  4574. ImGuiContext& g = *GImGui;
  4575. const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
  4576. return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
  4577. }
  4578. bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
  4579. {
  4580. ImGuiWindow* window = GetCurrentWindow();
  4581. if (window->SkipItems)
  4582. return false;
  4583. ImGuiContext& g = *GImGui;
  4584. const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
  4585. return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
  4586. }
  4587. bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
  4588. {
  4589. if (flags & ImGuiTreeNodeFlags_Leaf)
  4590. return true;
  4591. // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
  4592. ImGuiContext& g = *GImGui;
  4593. ImGuiWindow* window = g.CurrentWindow;
  4594. ImGuiStorage* storage = window->DC.StateStorage;
  4595. bool is_open;
  4596. if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen)
  4597. {
  4598. if (g.NextItemData.OpenCond & ImGuiCond_Always)
  4599. {
  4600. is_open = g.NextItemData.OpenVal;
  4601. storage->SetInt(id, is_open);
  4602. }
  4603. else
  4604. {
  4605. // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
  4606. const int stored_value = storage->GetInt(id, -1);
  4607. if (stored_value == -1)
  4608. {
  4609. is_open = g.NextItemData.OpenVal;
  4610. storage->SetInt(id, is_open);
  4611. }
  4612. else
  4613. {
  4614. is_open = stored_value != 0;
  4615. }
  4616. }
  4617. }
  4618. else
  4619. {
  4620. is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
  4621. }
  4622. // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
  4623. // NB- If we are above max depth we still allow manually opened nodes to be logged.
  4624. if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
  4625. is_open = true;
  4626. return is_open;
  4627. }
  4628. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
  4629. {
  4630. ImGuiWindow* window = GetCurrentWindow();
  4631. if (window->SkipItems)
  4632. return false;
  4633. ImGuiContext& g = *GImGui;
  4634. const ImGuiStyle& style = g.Style;
  4635. const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
  4636. const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
  4637. if (!label_end)
  4638. label_end = FindRenderedTextEnd(label);
  4639. const ImVec2 label_size = CalcTextSize(label, label_end, false);
  4640. // We vertically grow up to current line height up the typical widget height.
  4641. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
  4642. ImRect frame_bb;
  4643. frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
  4644. frame_bb.Min.y = window->DC.CursorPos.y;
  4645. frame_bb.Max.x = window->WorkRect.Max.x;
  4646. frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
  4647. if (display_frame)
  4648. {
  4649. // Framed header expand a little outside the default padding, to the edge of InnerClipRect
  4650. // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
  4651. frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f);
  4652. frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f);
  4653. }
  4654. const float text_offset_x = g.FontSize + (display_frame ? padding.x*3 : padding.x*2); // Collapser arrow width + Spacing
  4655. const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it
  4656. const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
  4657. ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
  4658. ItemSize(ImVec2(text_width, frame_height), padding.y);
  4659. // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
  4660. ImRect interact_bb = frame_bb;
  4661. if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0)
  4662. interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f;
  4663. // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
  4664. // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
  4665. // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
  4666. const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
  4667. bool is_open = TreeNodeBehaviorIsOpen(id, flags);
  4668. if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
  4669. window->DC.TreeMayJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
  4670. bool item_add = ItemAdd(interact_bb, id);
  4671. window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
  4672. window->DC.LastItemDisplayRect = frame_bb;
  4673. if (!item_add)
  4674. {
  4675. if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
  4676. TreePushOverrideID(id);
  4677. IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
  4678. return is_open;
  4679. }
  4680. // Flags that affects opening behavior:
  4681. // - 0 (default) .................... single-click anywhere to open
  4682. // - OpenOnDoubleClick .............. double-click anywhere to open
  4683. // - OpenOnArrow .................... single-click on arrow to open
  4684. // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
  4685. ImGuiButtonFlags button_flags = 0;
  4686. if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
  4687. button_flags |= ImGuiButtonFlags_AllowItemOverlap;
  4688. if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
  4689. button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
  4690. if (!is_leaf)
  4691. button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
  4692. // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
  4693. // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
  4694. // When clicking on the rest of the tree node we always disallow keyboard modifiers.
  4695. const float hit_padding_x = style.TouchExtraPadding.x;
  4696. const float arrow_hit_x1 = (text_pos.x - text_offset_x) - hit_padding_x;
  4697. const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + hit_padding_x;
  4698. if (window != g.HoveredWindow || !(g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2))
  4699. button_flags |= ImGuiButtonFlags_NoKeyModifiers;
  4700. bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
  4701. const bool was_selected = selected;
  4702. bool hovered, held;
  4703. bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
  4704. if (!is_leaf)
  4705. {
  4706. bool toggled = false;
  4707. if (pressed)
  4708. {
  4709. if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id))
  4710. toggled = true;
  4711. if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
  4712. toggled |= (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2) && (!g.NavDisableMouseHover); // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
  4713. if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseDoubleClicked[0])
  4714. toggled = true;
  4715. if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
  4716. toggled = false;
  4717. }
  4718. if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
  4719. {
  4720. toggled = true;
  4721. NavMoveRequestCancel();
  4722. }
  4723. if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
  4724. {
  4725. toggled = true;
  4726. NavMoveRequestCancel();
  4727. }
  4728. if (toggled)
  4729. {
  4730. is_open = !is_open;
  4731. window->DC.StateStorage->SetInt(id, is_open);
  4732. window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
  4733. }
  4734. }
  4735. if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
  4736. SetItemAllowOverlap();
  4737. // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
  4738. if (selected != was_selected) //-V547
  4739. window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
  4740. // Render
  4741. const ImU32 text_col = GetColorU32(ImGuiCol_Text);
  4742. ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
  4743. if (display_frame)
  4744. {
  4745. // Framed type
  4746. const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
  4747. RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
  4748. RenderNavHighlight(frame_bb, id, nav_highlight_flags);
  4749. if (flags & ImGuiTreeNodeFlags_Bullet)
  4750. RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
  4751. else if (!is_leaf)
  4752. RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
  4753. else // Leaf without bullet, left-adjusted text
  4754. text_pos.x -= text_offset_x;
  4755. if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
  4756. frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
  4757. if (g.LogEnabled)
  4758. {
  4759. // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
  4760. const char log_prefix[] = "\n##";
  4761. const char log_suffix[] = "##";
  4762. LogRenderedText(&text_pos, log_prefix, log_prefix+3);
  4763. RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
  4764. LogRenderedText(&text_pos, log_suffix, log_suffix+2);
  4765. }
  4766. else
  4767. {
  4768. RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
  4769. }
  4770. }
  4771. else
  4772. {
  4773. // Unframed typed for tree nodes
  4774. if (hovered || selected)
  4775. {
  4776. const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
  4777. RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
  4778. RenderNavHighlight(frame_bb, id, nav_highlight_flags);
  4779. }
  4780. if (flags & ImGuiTreeNodeFlags_Bullet)
  4781. RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
  4782. else if (!is_leaf)
  4783. RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
  4784. if (g.LogEnabled)
  4785. LogRenderedText(&text_pos, ">");
  4786. RenderText(text_pos, label, label_end, false);
  4787. }
  4788. if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
  4789. TreePushOverrideID(id);
  4790. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
  4791. return is_open;
  4792. }
  4793. void ImGui::TreePush(const char* str_id)
  4794. {
  4795. ImGuiWindow* window = GetCurrentWindow();
  4796. Indent();
  4797. window->DC.TreeDepth++;
  4798. PushID(str_id ? str_id : "#TreePush");
  4799. }
  4800. void ImGui::TreePush(const void* ptr_id)
  4801. {
  4802. ImGuiWindow* window = GetCurrentWindow();
  4803. Indent();
  4804. window->DC.TreeDepth++;
  4805. PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
  4806. }
  4807. void ImGui::TreePushOverrideID(ImGuiID id)
  4808. {
  4809. ImGuiWindow* window = GetCurrentWindow();
  4810. Indent();
  4811. window->DC.TreeDepth++;
  4812. window->IDStack.push_back(id);
  4813. }
  4814. void ImGui::TreePop()
  4815. {
  4816. ImGuiContext& g = *GImGui;
  4817. ImGuiWindow* window = g.CurrentWindow;
  4818. Unindent();
  4819. window->DC.TreeDepth--;
  4820. ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
  4821. // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
  4822. if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
  4823. if (g.NavIdIsAlive && (window->DC.TreeMayJumpToParentOnPopMask & tree_depth_mask))
  4824. {
  4825. SetNavID(window->IDStack.back(), g.NavLayer);
  4826. NavMoveRequestCancel();
  4827. }
  4828. window->DC.TreeMayJumpToParentOnPopMask &= tree_depth_mask - 1;
  4829. IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
  4830. PopID();
  4831. }
  4832. // Horizontal distance preceding label when using TreeNode() or Bullet()
  4833. float ImGui::GetTreeNodeToLabelSpacing()
  4834. {
  4835. ImGuiContext& g = *GImGui;
  4836. return g.FontSize + (g.Style.FramePadding.x * 2.0f);
  4837. }
  4838. // Set next TreeNode/CollapsingHeader open state.
  4839. void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
  4840. {
  4841. ImGuiContext& g = *GImGui;
  4842. if (g.CurrentWindow->SkipItems)
  4843. return;
  4844. g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen;
  4845. g.NextItemData.OpenVal = is_open;
  4846. g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always;
  4847. }
  4848. // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
  4849. // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
  4850. bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
  4851. {
  4852. ImGuiWindow* window = GetCurrentWindow();
  4853. if (window->SkipItems)
  4854. return false;
  4855. return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
  4856. }
  4857. bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
  4858. {
  4859. ImGuiWindow* window = GetCurrentWindow();
  4860. if (window->SkipItems)
  4861. return false;
  4862. if (p_open && !*p_open)
  4863. return false;
  4864. ImGuiID id = window->GetID(label);
  4865. flags |= ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton : 0);
  4866. bool is_open = TreeNodeBehavior(id, flags, label);
  4867. if (p_open)
  4868. {
  4869. // Create a small overlapping close button
  4870. // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
  4871. // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
  4872. ImGuiContext& g = *GImGui;
  4873. ImGuiItemHoveredDataBackup last_item_backup;
  4874. float button_size = g.FontSize;
  4875. float button_x = ImMax(window->DC.LastItemRect.Min.x, window->DC.LastItemRect.Max.x - g.Style.FramePadding.x * 2.0f - button_size);
  4876. float button_y = window->DC.LastItemRect.Min.y;
  4877. if (CloseButton(window->GetID((void*)((intptr_t)id + 1)), ImVec2(button_x, button_y)))
  4878. *p_open = false;
  4879. last_item_backup.Restore();
  4880. }
  4881. return is_open;
  4882. }
  4883. //-------------------------------------------------------------------------
  4884. // [SECTION] Widgets: Selectable
  4885. //-------------------------------------------------------------------------
  4886. // - Selectable()
  4887. //-------------------------------------------------------------------------
  4888. // Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
  4889. // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
  4890. bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
  4891. {
  4892. ImGuiWindow* window = GetCurrentWindow();
  4893. if (window->SkipItems)
  4894. return false;
  4895. ImGuiContext& g = *GImGui;
  4896. const ImGuiStyle& style = g.Style;
  4897. if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns) // FIXME-OPT: Avoid if vertically clipped.
  4898. PushColumnsBackground();
  4899. ImGuiID id = window->GetID(label);
  4900. ImVec2 label_size = CalcTextSize(label, NULL, true);
  4901. ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
  4902. ImVec2 pos = window->DC.CursorPos;
  4903. pos.y += window->DC.CurrLineTextBaseOffset;
  4904. ImRect bb_inner(pos, pos + size);
  4905. ItemSize(size, 0.0f);
  4906. // Fill horizontal space.
  4907. ImVec2 window_padding = window->WindowPadding;
  4908. float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
  4909. float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
  4910. ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
  4911. ImRect bb(pos, pos + size_draw);
  4912. if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
  4913. bb.Max.x += window_padding.x;
  4914. // Selectables are tightly packed together so we extend the box to cover spacing between selectable.
  4915. const float spacing_x = style.ItemSpacing.x;
  4916. const float spacing_y = style.ItemSpacing.y;
  4917. const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
  4918. const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
  4919. bb.Min.x -= spacing_L;
  4920. bb.Min.y -= spacing_U;
  4921. bb.Max.x += (spacing_x - spacing_L);
  4922. bb.Max.y += (spacing_y - spacing_U);
  4923. bool item_add;
  4924. if (flags & ImGuiSelectableFlags_Disabled)
  4925. {
  4926. ImGuiItemFlags backup_item_flags = window->DC.ItemFlags;
  4927. window->DC.ItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
  4928. item_add = ItemAdd(bb, id);
  4929. window->DC.ItemFlags = backup_item_flags;
  4930. }
  4931. else
  4932. {
  4933. item_add = ItemAdd(bb, id);
  4934. }
  4935. if (!item_add)
  4936. {
  4937. if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
  4938. PopColumnsBackground();
  4939. return false;
  4940. }
  4941. // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
  4942. ImGuiButtonFlags button_flags = 0;
  4943. if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
  4944. if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
  4945. if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
  4946. if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
  4947. if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
  4948. if (flags & ImGuiSelectableFlags_AllowItemOverlap) button_flags |= ImGuiButtonFlags_AllowItemOverlap;
  4949. if (flags & ImGuiSelectableFlags_Disabled)
  4950. selected = false;
  4951. const bool was_selected = selected;
  4952. bool hovered, held;
  4953. bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
  4954. // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard
  4955. if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
  4956. {
  4957. if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
  4958. {
  4959. g.NavDisableHighlight = true;
  4960. SetNavID(id, window->DC.NavLayerCurrent);
  4961. }
  4962. }
  4963. if (pressed)
  4964. MarkItemEdited(id);
  4965. if (flags & ImGuiSelectableFlags_AllowItemOverlap)
  4966. SetItemAllowOverlap();
  4967. // In this branch, Selectable() cannot toggle the selection so this will never trigger.
  4968. if (selected != was_selected) //-V547
  4969. window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
  4970. // Render
  4971. if (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld))
  4972. hovered = true;
  4973. if (hovered || selected)
  4974. {
  4975. const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
  4976. RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
  4977. RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
  4978. }
  4979. if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
  4980. {
  4981. PopColumnsBackground();
  4982. bb.Max.x -= (GetContentRegionMax().x - max_x);
  4983. }
  4984. if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
  4985. RenderTextClipped(bb_inner.Min, bb_inner.Max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
  4986. if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
  4987. // Automatically close popups
  4988. if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
  4989. CloseCurrentPopup();
  4990. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
  4991. return pressed;
  4992. }
  4993. bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
  4994. {
  4995. if (Selectable(label, *p_selected, flags, size_arg))
  4996. {
  4997. *p_selected = !*p_selected;
  4998. return true;
  4999. }
  5000. return false;
  5001. }
  5002. //-------------------------------------------------------------------------
  5003. // [SECTION] Widgets: ListBox
  5004. //-------------------------------------------------------------------------
  5005. // - ListBox()
  5006. // - ListBoxHeader()
  5007. // - ListBoxFooter()
  5008. //-------------------------------------------------------------------------
  5009. // FIXME: This is an old API. We should redesign some of it, rename ListBoxHeader->BeginListBox, ListBoxFooter->EndListBox
  5010. // and promote using them over existing ListBox() functions, similarly to change with combo boxes.
  5011. //-------------------------------------------------------------------------
  5012. // FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
  5013. // Helper to calculate the size of a listbox and display a label on the right.
  5014. // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
  5015. bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
  5016. {
  5017. ImGuiContext& g = *GImGui;
  5018. ImGuiWindow* window = GetCurrentWindow();
  5019. if (window->SkipItems)
  5020. return false;
  5021. const ImGuiStyle& style = g.Style;
  5022. const ImGuiID id = GetID(label);
  5023. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  5024. // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
  5025. ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
  5026. ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
  5027. ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
  5028. ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
  5029. window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
  5030. g.NextItemData.ClearFlags();
  5031. if (!IsRectVisible(bb.Min, bb.Max))
  5032. {
  5033. ItemSize(bb.GetSize(), style.FramePadding.y);
  5034. ItemAdd(bb, 0, &frame_bb);
  5035. return false;
  5036. }
  5037. BeginGroup();
  5038. if (label_size.x > 0)
  5039. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
  5040. BeginChildFrame(id, frame_bb.GetSize());
  5041. return true;
  5042. }
  5043. // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
  5044. bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
  5045. {
  5046. // Size default to hold ~7.25 items.
  5047. // We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
  5048. // We don't add this extra bit if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
  5049. // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
  5050. if (height_in_items < 0)
  5051. height_in_items = ImMin(items_count, 7);
  5052. const ImGuiStyle& style = GetStyle();
  5053. float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);
  5054. // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
  5055. ImVec2 size;
  5056. size.x = 0.0f;
  5057. size.y = ImFloor(GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f);
  5058. return ListBoxHeader(label, size);
  5059. }
  5060. // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
  5061. void ImGui::ListBoxFooter()
  5062. {
  5063. ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
  5064. const ImRect bb = parent_window->DC.LastItemRect;
  5065. const ImGuiStyle& style = GetStyle();
  5066. EndChildFrame();
  5067. // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
  5068. // We call SameLine() to restore DC.CurrentLine* data
  5069. SameLine();
  5070. parent_window->DC.CursorPos = bb.Min;
  5071. ItemSize(bb, style.FramePadding.y);
  5072. EndGroup();
  5073. }
  5074. bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
  5075. {
  5076. const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
  5077. return value_changed;
  5078. }
  5079. bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
  5080. {
  5081. if (!ListBoxHeader(label, items_count, height_in_items))
  5082. return false;
  5083. // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
  5084. ImGuiContext& g = *GImGui;
  5085. bool value_changed = false;
  5086. ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
  5087. while (clipper.Step())
  5088. for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
  5089. {
  5090. const bool item_selected = (i == *current_item);
  5091. const char* item_text;
  5092. if (!items_getter(data, i, &item_text))
  5093. item_text = "*Unknown item*";
  5094. PushID(i);
  5095. if (Selectable(item_text, item_selected))
  5096. {
  5097. *current_item = i;
  5098. value_changed = true;
  5099. }
  5100. if (item_selected)
  5101. SetItemDefaultFocus();
  5102. PopID();
  5103. }
  5104. ListBoxFooter();
  5105. if (value_changed)
  5106. MarkItemEdited(g.CurrentWindow->DC.LastItemId);
  5107. return value_changed;
  5108. }
  5109. //-------------------------------------------------------------------------
  5110. // [SECTION] Widgets: PlotLines, PlotHistogram
  5111. //-------------------------------------------------------------------------
  5112. // - PlotEx() [Internal]
  5113. // - PlotLines()
  5114. // - PlotHistogram()
  5115. //-------------------------------------------------------------------------
  5116. void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size)
  5117. {
  5118. ImGuiWindow* window = GetCurrentWindow();
  5119. if (window->SkipItems)
  5120. return;
  5121. ImGuiContext& g = *GImGui;
  5122. const ImGuiStyle& style = g.Style;
  5123. const ImGuiID id = window->GetID(label);
  5124. const ImVec2 label_size = CalcTextSize(label, NULL, true);
  5125. if (frame_size.x == 0.0f)
  5126. frame_size.x = CalcItemWidth();
  5127. if (frame_size.y == 0.0f)
  5128. frame_size.y = label_size.y + (style.FramePadding.y * 2);
  5129. const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
  5130. const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
  5131. const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
  5132. ItemSize(total_bb, style.FramePadding.y);
  5133. if (!ItemAdd(total_bb, 0, &frame_bb))
  5134. return;
  5135. const bool hovered = ItemHoverable(frame_bb, id);
  5136. // Determine scale from values if not specified
  5137. if (scale_min == FLT_MAX || scale_max == FLT_MAX)
  5138. {
  5139. float v_min = FLT_MAX;
  5140. float v_max = -FLT_MAX;
  5141. for (int i = 0; i < values_count; i++)
  5142. {
  5143. const float v = values_getter(data, i);
  5144. if (v != v) // Ignore NaN values
  5145. continue;
  5146. v_min = ImMin(v_min, v);
  5147. v_max = ImMax(v_max, v);
  5148. }
  5149. if (scale_min == FLT_MAX)
  5150. scale_min = v_min;
  5151. if (scale_max == FLT_MAX)
  5152. scale_max = v_max;
  5153. }
  5154. RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
  5155. const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
  5156. if (values_count >= values_count_min)
  5157. {
  5158. int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
  5159. int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
  5160. // Tooltip on hover
  5161. int v_hovered = -1;
  5162. if (hovered && inner_bb.Contains(g.IO.MousePos))
  5163. {
  5164. const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
  5165. const int v_idx = (int)(t * item_count);
  5166. IM_ASSERT(v_idx >= 0 && v_idx < values_count);
  5167. const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
  5168. const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
  5169. if (plot_type == ImGuiPlotType_Lines)
  5170. SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
  5171. else if (plot_type == ImGuiPlotType_Histogram)
  5172. SetTooltip("%d: %8.4g", v_idx, v0);
  5173. v_hovered = v_idx;
  5174. }
  5175. const float t_step = 1.0f / (float)res_w;
  5176. const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
  5177. float v0 = values_getter(data, (0 + values_offset) % values_count);
  5178. float t0 = 0.0f;
  5179. ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
  5180. float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
  5181. const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
  5182. const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
  5183. for (int n = 0; n < res_w; n++)
  5184. {
  5185. const float t1 = t0 + t_step;
  5186. const int v1_idx = (int)(t0 * item_count + 0.5f);
  5187. IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
  5188. const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
  5189. const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
  5190. // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
  5191. ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
  5192. ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
  5193. if (plot_type == ImGuiPlotType_Lines)
  5194. {
  5195. window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
  5196. }
  5197. else if (plot_type == ImGuiPlotType_Histogram)
  5198. {
  5199. if (pos1.x >= pos0.x + 2.0f)
  5200. pos1.x -= 1.0f;
  5201. window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
  5202. }
  5203. t0 = t1;
  5204. tp0 = tp1;
  5205. }
  5206. }
  5207. // Text overlay
  5208. if (overlay_text)
  5209. RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
  5210. if (label_size.x > 0.0f)
  5211. RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
  5212. }
  5213. struct ImGuiPlotArrayGetterData
  5214. {
  5215. const float* Values;
  5216. int Stride;
  5217. ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
  5218. };
  5219. static float Plot_ArrayGetter(void* data, int idx)
  5220. {
  5221. ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
  5222. const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
  5223. return v;
  5224. }
  5225. void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
  5226. {
  5227. ImGuiPlotArrayGetterData data(values, stride);
  5228. PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
  5229. }
  5230. void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
  5231. {
  5232. PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
  5233. }
  5234. void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
  5235. {
  5236. ImGuiPlotArrayGetterData data(values, stride);
  5237. PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
  5238. }
  5239. void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
  5240. {
  5241. PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
  5242. }
  5243. //-------------------------------------------------------------------------
  5244. // [SECTION] Widgets: Value helpers
  5245. // Those is not very useful, legacy API.
  5246. //-------------------------------------------------------------------------
  5247. // - Value()
  5248. //-------------------------------------------------------------------------
  5249. void ImGui::Value(const char* prefix, bool b)
  5250. {
  5251. Text("%s: %s", prefix, (b ? "true" : "false"));
  5252. }
  5253. void ImGui::Value(const char* prefix, int v)
  5254. {
  5255. Text("%s: %d", prefix, v);
  5256. }
  5257. void ImGui::Value(const char* prefix, unsigned int v)
  5258. {
  5259. Text("%s: %d", prefix, v);
  5260. }
  5261. void ImGui::Value(const char* prefix, float v, const char* float_format)
  5262. {
  5263. if (float_format)
  5264. {
  5265. char fmt[64];
  5266. ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
  5267. Text(fmt, prefix, v);
  5268. }
  5269. else
  5270. {
  5271. Text("%s: %.3f", prefix, v);
  5272. }
  5273. }
  5274. //-------------------------------------------------------------------------
  5275. // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
  5276. //-------------------------------------------------------------------------
  5277. // - ImGuiMenuColumns [Internal]
  5278. // - BeginMenuBar()
  5279. // - EndMenuBar()
  5280. // - BeginMainMenuBar()
  5281. // - EndMainMenuBar()
  5282. // - BeginMenu()
  5283. // - EndMenu()
  5284. // - MenuItem()
  5285. //-------------------------------------------------------------------------
  5286. // Helpers for internal use
  5287. ImGuiMenuColumns::ImGuiMenuColumns()
  5288. {
  5289. Spacing = Width = NextWidth = 0.0f;
  5290. memset(Pos, 0, sizeof(Pos));
  5291. memset(NextWidths, 0, sizeof(NextWidths));
  5292. }
  5293. void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
  5294. {
  5295. IM_ASSERT(count == IM_ARRAYSIZE(Pos));
  5296. IM_UNUSED(count);
  5297. Width = NextWidth = 0.0f;
  5298. Spacing = spacing;
  5299. if (clear)
  5300. memset(NextWidths, 0, sizeof(NextWidths));
  5301. for (int i = 0; i < IM_ARRAYSIZE(Pos); i++)
  5302. {
  5303. if (i > 0 && NextWidths[i] > 0.0f)
  5304. Width += Spacing;
  5305. Pos[i] = IM_FLOOR(Width);
  5306. Width += NextWidths[i];
  5307. NextWidths[i] = 0.0f;
  5308. }
  5309. }
  5310. float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
  5311. {
  5312. NextWidth = 0.0f;
  5313. NextWidths[0] = ImMax(NextWidths[0], w0);
  5314. NextWidths[1] = ImMax(NextWidths[1], w1);
  5315. NextWidths[2] = ImMax(NextWidths[2], w2);
  5316. for (int i = 0; i < IM_ARRAYSIZE(Pos); i++)
  5317. NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
  5318. return ImMax(Width, NextWidth);
  5319. }
  5320. float ImGuiMenuColumns::CalcExtraSpace(float avail_w) const
  5321. {
  5322. return ImMax(0.0f, avail_w - Width);
  5323. }
  5324. // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
  5325. // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
  5326. // Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
  5327. // Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
  5328. bool ImGui::BeginMenuBar()
  5329. {
  5330. ImGuiWindow* window = GetCurrentWindow();
  5331. if (window->SkipItems)
  5332. return false;
  5333. if (!(window->Flags & ImGuiWindowFlags_MenuBar))
  5334. return false;
  5335. IM_ASSERT(!window->DC.MenuBarAppending);
  5336. BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
  5337. PushID("##menubar");
  5338. // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
  5339. // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
  5340. ImRect bar_rect = window->MenuBarRect();
  5341. ImRect clip_rect(IM_ROUND(bar_rect.Min.x), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding)), IM_ROUND(bar_rect.Max.y));
  5342. clip_rect.ClipWith(window->OuterRectClipped);
  5343. PushClipRect(clip_rect.Min, clip_rect.Max, false);
  5344. window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
  5345. window->DC.LayoutType = ImGuiLayoutType_Horizontal;
  5346. window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
  5347. window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
  5348. window->DC.MenuBarAppending = true;
  5349. AlignTextToFramePadding();
  5350. return true;
  5351. }
  5352. void ImGui::EndMenuBar()
  5353. {
  5354. ImGuiWindow* window = GetCurrentWindow();
  5355. if (window->SkipItems)
  5356. return;
  5357. ImGuiContext& g = *GImGui;
  5358. // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
  5359. if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
  5360. {
  5361. ImGuiWindow* nav_earliest_child = g.NavWindow;
  5362. while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
  5363. nav_earliest_child = nav_earliest_child->ParentWindow;
  5364. if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
  5365. {
  5366. // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
  5367. // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
  5368. const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
  5369. IM_ASSERT(window->DC.NavLayerActiveMaskNext & (1 << layer)); // Sanity check
  5370. FocusWindow(window);
  5371. SetNavIDWithRectRel(window->NavLastIds[layer], layer, window->NavRectRel[layer]);
  5372. g.NavLayer = layer;
  5373. g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
  5374. g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
  5375. NavMoveRequestCancel();
  5376. }
  5377. }
  5378. IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
  5379. IM_ASSERT(window->DC.MenuBarAppending);
  5380. PopClipRect();
  5381. PopID();
  5382. window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
  5383. window->DC.GroupStack.back().EmitItem = false;
  5384. EndGroup(); // Restore position on layer 0
  5385. window->DC.LayoutType = ImGuiLayoutType_Vertical;
  5386. window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
  5387. window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
  5388. window->DC.MenuBarAppending = false;
  5389. }
  5390. // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
  5391. bool ImGui::BeginMainMenuBar()
  5392. {
  5393. ImGuiContext& g = *GImGui;
  5394. g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
  5395. SetNextWindowPos(ImVec2(0.0f, 0.0f));
  5396. SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
  5397. PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
  5398. PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
  5399. ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
  5400. bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
  5401. PopStyleVar(2);
  5402. g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
  5403. if (!is_open)
  5404. {
  5405. End();
  5406. return false;
  5407. }
  5408. return true; //-V1020
  5409. }
  5410. void ImGui::EndMainMenuBar()
  5411. {
  5412. EndMenuBar();
  5413. // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
  5414. // FIXME: With this strategy we won't be able to restore a NULL focus.
  5415. ImGuiContext& g = *GImGui;
  5416. if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0 && !g.NavAnyRequest)
  5417. FocusTopMostWindowUnderOne(g.NavWindow, NULL);
  5418. End();
  5419. }
  5420. bool ImGui::BeginMenu(const char* label, bool enabled)
  5421. {
  5422. ImGuiWindow* window = GetCurrentWindow();
  5423. if (window->SkipItems)
  5424. return false;
  5425. ImGuiContext& g = *GImGui;
  5426. const ImGuiStyle& style = g.Style;
  5427. const ImGuiID id = window->GetID(label);
  5428. ImVec2 label_size = CalcTextSize(label, NULL, true);
  5429. bool pressed;
  5430. bool menu_is_open = IsPopupOpen(id);
  5431. bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
  5432. ImGuiWindow* backed_nav_window = g.NavWindow;
  5433. if (menuset_is_open)
  5434. g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
  5435. // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
  5436. // However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().
  5437. // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
  5438. ImVec2 popup_pos, pos = window->DC.CursorPos;
  5439. if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
  5440. {
  5441. // Menu inside an horizontal menu bar
  5442. // Selectable extend their highlight by half ItemSpacing in each direction.
  5443. // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
  5444. popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
  5445. window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
  5446. PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
  5447. float w = label_size.x;
  5448. pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
  5449. PopStyleVar();
  5450. window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
  5451. }
  5452. else
  5453. {
  5454. // Menu inside a menu
  5455. popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
  5456. float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, IM_FLOOR(g.FontSize * 1.20f)); // Feedback to next frame
  5457. float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
  5458. pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
  5459. ImU32 text_col = GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled);
  5460. RenderArrow(window->DrawList, pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), text_col, ImGuiDir_Right);
  5461. }
  5462. const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
  5463. if (menuset_is_open)
  5464. g.NavWindow = backed_nav_window;
  5465. bool want_open = false;
  5466. bool want_close = false;
  5467. if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
  5468. {
  5469. // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
  5470. // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
  5471. bool moving_toward_other_child_menu = false;
  5472. ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
  5473. if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
  5474. {
  5475. // FIXME-DPI: Values should be derived from a master "scale" factor.
  5476. ImRect next_window_rect = child_menu_window->Rect();
  5477. ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
  5478. ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
  5479. ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
  5480. float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
  5481. ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
  5482. tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
  5483. tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
  5484. moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
  5485. //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
  5486. }
  5487. if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
  5488. want_close = true;
  5489. if (!menu_is_open && hovered && pressed) // Click to open
  5490. want_open = true;
  5491. else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
  5492. want_open = true;
  5493. if (g.NavActivateId == id)
  5494. {
  5495. want_close = menu_is_open;
  5496. want_open = !menu_is_open;
  5497. }
  5498. if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
  5499. {
  5500. want_open = true;
  5501. NavMoveRequestCancel();
  5502. }
  5503. }
  5504. else
  5505. {
  5506. // Menu bar
  5507. if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
  5508. {
  5509. want_close = true;
  5510. want_open = menu_is_open = false;
  5511. }
  5512. else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
  5513. {
  5514. want_open = true;
  5515. }
  5516. else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
  5517. {
  5518. want_open = true;
  5519. NavMoveRequestCancel();
  5520. }
  5521. }
  5522. if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
  5523. want_close = true;
  5524. if (want_close && IsPopupOpen(id))
  5525. ClosePopupToLevel(g.BeginPopupStack.Size, true);
  5526. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
  5527. if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
  5528. {
  5529. // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
  5530. OpenPopup(label);
  5531. return false;
  5532. }
  5533. menu_is_open |= want_open;
  5534. if (want_open)
  5535. OpenPopup(label);
  5536. if (menu_is_open)
  5537. {
  5538. // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
  5539. SetNextWindowPos(popup_pos, ImGuiCond_Always);
  5540. ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
  5541. if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
  5542. flags |= ImGuiWindowFlags_ChildWindow;
  5543. menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
  5544. }
  5545. return menu_is_open;
  5546. }
  5547. void ImGui::EndMenu()
  5548. {
  5549. // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
  5550. // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
  5551. // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
  5552. ImGuiContext& g = *GImGui;
  5553. ImGuiWindow* window = g.CurrentWindow;
  5554. if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
  5555. {
  5556. ClosePopupToLevel(g.BeginPopupStack.Size, true);
  5557. NavMoveRequestCancel();
  5558. }
  5559. EndPopup();
  5560. }
  5561. bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
  5562. {
  5563. ImGuiWindow* window = GetCurrentWindow();
  5564. if (window->SkipItems)
  5565. return false;
  5566. ImGuiContext& g = *GImGui;
  5567. ImGuiStyle& style = g.Style;
  5568. ImVec2 pos = window->DC.CursorPos;
  5569. ImVec2 label_size = CalcTextSize(label, NULL, true);
  5570. // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
  5571. // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
  5572. ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | ImGuiSelectableFlags_SetNavIdOnHover | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
  5573. bool pressed;
  5574. if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
  5575. {
  5576. // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
  5577. // Note that in this situation we render neither the shortcut neither the selected tick mark
  5578. float w = label_size.x;
  5579. window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f);
  5580. PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y));
  5581. pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
  5582. PopStyleVar();
  5583. window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
  5584. }
  5585. else
  5586. {
  5587. ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
  5588. float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, IM_FLOOR(g.FontSize * 1.20f)); // Feedback for next frame
  5589. float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
  5590. pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
  5591. if (shortcut_size.x > 0.0f)
  5592. {
  5593. PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
  5594. RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
  5595. PopStyleColor();
  5596. }
  5597. if (selected)
  5598. RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
  5599. }
  5600. IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
  5601. return pressed;
  5602. }
  5603. bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
  5604. {
  5605. if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
  5606. {
  5607. if (p_selected)
  5608. *p_selected = !*p_selected;
  5609. return true;
  5610. }
  5611. return false;
  5612. }
  5613. //-------------------------------------------------------------------------
  5614. // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
  5615. //-------------------------------------------------------------------------
  5616. // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
  5617. // and some of the construct which are not used in Master may be left here to facilitate merging.
  5618. //-------------------------------------------------------------------------
  5619. // - BeginTabBar()
  5620. // - BeginTabBarEx() [Internal]
  5621. // - EndTabBar()
  5622. // - TabBarLayout() [Internal]
  5623. // - TabBarCalcTabID() [Internal]
  5624. // - TabBarCalcMaxTabWidth() [Internal]
  5625. // - TabBarFindTabById() [Internal]
  5626. // - TabBarRemoveTab() [Internal]
  5627. // - TabBarCloseTab() [Internal]
  5628. // - TabBarScrollClamp()v
  5629. // - TabBarScrollToTab() [Internal]
  5630. // - TabBarQueueChangeTabOrder() [Internal]
  5631. // - TabBarScrollingButtons() [Internal]
  5632. // - TabBarTabListPopupButton() [Internal]
  5633. //-------------------------------------------------------------------------
  5634. namespace ImGui
  5635. {
  5636. static void TabBarLayout(ImGuiTabBar* tab_bar);
  5637. static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
  5638. static float TabBarCalcMaxTabWidth();
  5639. static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
  5640. static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
  5641. static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);
  5642. static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
  5643. }
  5644. ImGuiTabBar::ImGuiTabBar()
  5645. {
  5646. ID = 0;
  5647. SelectedTabId = NextSelectedTabId = VisibleTabId = 0;
  5648. CurrFrameVisible = PrevFrameVisible = -1;
  5649. LastTabContentHeight = 0.0f;
  5650. OffsetMax = OffsetMaxIdeal = OffsetNextTab = 0.0f;
  5651. ScrollingAnim = ScrollingTarget = ScrollingTargetDistToVisibility = ScrollingSpeed = 0.0f;
  5652. Flags = ImGuiTabBarFlags_None;
  5653. ReorderRequestTabId = 0;
  5654. ReorderRequestDir = 0;
  5655. WantLayout = VisibleTabWasSubmitted = false;
  5656. LastTabItemIdx = -1;
  5657. }
  5658. static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)
  5659. {
  5660. const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
  5661. const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
  5662. return (int)(a->Offset - b->Offset);
  5663. }
  5664. static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
  5665. {
  5666. ImGuiContext& g = *GImGui;
  5667. return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
  5668. }
  5669. static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
  5670. {
  5671. ImGuiContext& g = *GImGui;
  5672. if (g.TabBars.Contains(tab_bar))
  5673. return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
  5674. return ImGuiPtrOrIndex(tab_bar);
  5675. }
  5676. bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
  5677. {
  5678. ImGuiContext& g = *GImGui;
  5679. ImGuiWindow* window = g.CurrentWindow;
  5680. if (window->SkipItems)
  5681. return false;
  5682. ImGuiID id = window->GetID(str_id);
  5683. ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
  5684. ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
  5685. tab_bar->ID = id;
  5686. return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
  5687. }
  5688. bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
  5689. {
  5690. ImGuiContext& g = *GImGui;
  5691. ImGuiWindow* window = g.CurrentWindow;
  5692. if (window->SkipItems)
  5693. return false;
  5694. if ((flags & ImGuiTabBarFlags_DockNode) == 0)
  5695. PushOverrideID(tab_bar->ID);
  5696. // Add to stack
  5697. g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
  5698. g.CurrentTabBar = tab_bar;
  5699. if (tab_bar->CurrFrameVisible == g.FrameCount)
  5700. {
  5701. //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
  5702. IM_ASSERT(0);
  5703. return true;
  5704. }
  5705. // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
  5706. // Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user.
  5707. if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)
  5708. ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);
  5709. // Flags
  5710. if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
  5711. flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
  5712. tab_bar->Flags = flags;
  5713. tab_bar->BarRect = tab_bar_bb;
  5714. tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
  5715. tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
  5716. tab_bar->CurrFrameVisible = g.FrameCount;
  5717. tab_bar->FramePadding = g.Style.FramePadding;
  5718. // Layout
  5719. ItemSize(ImVec2(tab_bar->OffsetMaxIdeal, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
  5720. window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
  5721. // Draw separator
  5722. const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive);
  5723. const float y = tab_bar->BarRect.Max.y - 1.0f;
  5724. {
  5725. const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f);
  5726. const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f);
  5727. window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
  5728. }
  5729. return true;
  5730. }
  5731. void ImGui::EndTabBar()
  5732. {
  5733. ImGuiContext& g = *GImGui;
  5734. ImGuiWindow* window = g.CurrentWindow;
  5735. if (window->SkipItems)
  5736. return;
  5737. ImGuiTabBar* tab_bar = g.CurrentTabBar;
  5738. if (tab_bar == NULL)
  5739. {
  5740. IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
  5741. return;
  5742. }
  5743. if (tab_bar->WantLayout)
  5744. TabBarLayout(tab_bar);
  5745. // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
  5746. const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
  5747. if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
  5748. tab_bar->LastTabContentHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);
  5749. else
  5750. window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->LastTabContentHeight;
  5751. if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
  5752. PopID();
  5753. g.CurrentTabBarStack.pop_back();
  5754. g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
  5755. }
  5756. // This is called only once a frame before by the first call to ItemTab()
  5757. // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
  5758. static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
  5759. {
  5760. ImGuiContext& g = *GImGui;
  5761. tab_bar->WantLayout = false;
  5762. // Garbage collect
  5763. int tab_dst_n = 0;
  5764. for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
  5765. {
  5766. ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
  5767. if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)
  5768. {
  5769. if (tab->ID == tab_bar->SelectedTabId)
  5770. tab_bar->SelectedTabId = 0;
  5771. continue;
  5772. }
  5773. if (tab_dst_n != tab_src_n)
  5774. tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
  5775. tab_dst_n++;
  5776. }
  5777. if (tab_bar->Tabs.Size != tab_dst_n)
  5778. tab_bar->Tabs.resize(tab_dst_n);
  5779. // Setup next selected tab
  5780. ImGuiID scroll_track_selected_tab_id = 0;
  5781. if (tab_bar->NextSelectedTabId)
  5782. {
  5783. tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
  5784. tab_bar->NextSelectedTabId = 0;
  5785. scroll_track_selected_tab_id = tab_bar->SelectedTabId;
  5786. }
  5787. // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
  5788. if (tab_bar->ReorderRequestTabId != 0)
  5789. {
  5790. if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))
  5791. {
  5792. //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
  5793. int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
  5794. if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)
  5795. {
  5796. ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
  5797. ImGuiTabItem item_tmp = *tab1;
  5798. *tab1 = *tab2;
  5799. *tab2 = item_tmp;
  5800. if (tab2->ID == tab_bar->SelectedTabId)
  5801. scroll_track_selected_tab_id = tab2->ID;
  5802. tab1 = tab2 = NULL;
  5803. }
  5804. if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
  5805. MarkIniSettingsDirty();
  5806. }
  5807. tab_bar->ReorderRequestTabId = 0;
  5808. }
  5809. // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
  5810. const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
  5811. if (tab_list_popup_button)
  5812. if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x!
  5813. scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
  5814. // Compute ideal widths
  5815. g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
  5816. float width_total_contents = 0.0f;
  5817. ImGuiTabItem* most_recently_selected_tab = NULL;
  5818. bool found_selected_tab_id = false;
  5819. for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
  5820. {
  5821. ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
  5822. IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
  5823. if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
  5824. most_recently_selected_tab = tab;
  5825. if (tab->ID == tab_bar->SelectedTabId)
  5826. found_selected_tab_id = true;
  5827. // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
  5828. // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
  5829. // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
  5830. const char* tab_name = tab_bar->GetTabName(tab);
  5831. const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
  5832. tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).x;
  5833. width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->ContentWidth;
  5834. // Store data so we can build an array sorted by width if we need to shrink tabs down
  5835. g.ShrinkWidthBuffer[tab_n].Index = tab_n;
  5836. g.ShrinkWidthBuffer[tab_n].Width = tab->ContentWidth;
  5837. }
  5838. // Compute width
  5839. const float initial_offset_x = 0.0f; // g.Style.ItemInnerSpacing.x;
  5840. const float width_avail = ImMax(tab_bar->BarRect.GetWidth() - initial_offset_x, 0.0f);
  5841. float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;
  5842. if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))
  5843. {
  5844. // If we don't have enough room, resize down the largest tabs first
  5845. ShrinkWidths(g.ShrinkWidthBuffer.Data, g.ShrinkWidthBuffer.Size, width_excess);
  5846. for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
  5847. tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index].Width = IM_FLOOR(g.ShrinkWidthBuffer[tab_n].Width);
  5848. }
  5849. else
  5850. {
  5851. const float tab_max_width = TabBarCalcMaxTabWidth();
  5852. for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
  5853. {
  5854. ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
  5855. tab->Width = ImMin(tab->ContentWidth, tab_max_width);
  5856. IM_ASSERT(tab->Width > 0.0f);
  5857. }
  5858. }
  5859. // Layout all active tabs
  5860. float offset_x = initial_offset_x;
  5861. float offset_x_ideal = offset_x;
  5862. tab_bar->OffsetNextTab = offset_x; // This is used by non-reorderable tab bar where the submission order is always honored.
  5863. for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
  5864. {
  5865. ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
  5866. tab->Offset = offset_x;
  5867. if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)
  5868. scroll_track_selected_tab_id = tab->ID;
  5869. offset_x += tab->Width + g.Style.ItemInnerSpacing.x;
  5870. offset_x_ideal += tab->ContentWidth + g.Style.ItemInnerSpacing.x;
  5871. }
  5872. tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);
  5873. tab_bar->OffsetMaxIdeal = ImMax(offset_x_ideal - g.Style.ItemInnerSpacing.x, 0.0f);
  5874. // Horizontal scrolling buttons
  5875. const bool scrolling_buttons = (tab_bar->OffsetMax > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll);
  5876. if (scrolling_buttons)
  5877. if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!
  5878. scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
  5879. // If we have lost the selected tab, select the next most recently active one
  5880. if (found_selected_tab_id == false)
  5881. tab_bar->SelectedTabId = 0;
  5882. if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
  5883. scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
  5884. // Lock in visible tab
  5885. tab_bar->VisibleTabId = tab_bar->SelectedTabId;
  5886. tab_bar->VisibleTabWasSubmitted = false;
  5887. // Update scrolling
  5888. if (scroll_track_selected_tab_id)
  5889. if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))
  5890. TabBarScrollToTab(tab_bar, scroll_track_selected_tab);
  5891. tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
  5892. tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
  5893. if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
  5894. {
  5895. // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
  5896. // Teleport if we are aiming far off the visible line
  5897. tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
  5898. tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
  5899. const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
  5900. tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
  5901. }
  5902. else
  5903. {
  5904. tab_bar->ScrollingSpeed = 0.0f;
  5905. }
  5906. // Clear name buffers
  5907. if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
  5908. tab_bar->TabsNames.Buf.resize(0);
  5909. }
  5910. // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
  5911. static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
  5912. {
  5913. if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
  5914. {
  5915. ImGuiID id = ImHashStr(label);
  5916. KeepAliveID(id);
  5917. return id;
  5918. }
  5919. else
  5920. {
  5921. ImGuiWindow* window = GImGui->CurrentWindow;
  5922. return window->GetID(label);
  5923. }
  5924. }
  5925. static float ImGui::TabBarCalcMaxTabWidth()
  5926. {
  5927. ImGuiContext& g = *GImGui;
  5928. return g.FontSize * 20.0f;
  5929. }
  5930. ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
  5931. {
  5932. if (tab_id != 0)
  5933. for (int n = 0; n < tab_bar->Tabs.Size; n++)
  5934. if (tab_bar->Tabs[n].ID == tab_id)
  5935. return &tab_bar->Tabs[n];
  5936. return NULL;
  5937. }
  5938. // The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
  5939. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
  5940. {
  5941. if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
  5942. tab_bar->Tabs.erase(tab);
  5943. if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }
  5944. if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }
  5945. if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
  5946. }
  5947. // Called on manual closure attempt
  5948. void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
  5949. {
  5950. if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
  5951. {
  5952. // This will remove a frame of lag for selecting another tab on closure.
  5953. // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
  5954. tab->LastFrameVisible = -1;
  5955. tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
  5956. }
  5957. else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
  5958. {
  5959. // Actually select before expecting closure
  5960. tab_bar->NextSelectedTabId = tab->ID;
  5961. }
  5962. }
  5963. static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
  5964. {
  5965. scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());
  5966. return ImMax(scrolling, 0.0f);
  5967. }
  5968. static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
  5969. {
  5970. ImGuiContext& g = *GImGui;
  5971. float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
  5972. int order = tab_bar->GetTabOrder(tab);
  5973. float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);
  5974. float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);
  5975. tab_bar->ScrollingTargetDistToVisibility = 0.0f;
  5976. if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= tab_bar->BarRect.GetWidth()))
  5977. {
  5978. tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
  5979. tab_bar->ScrollingTarget = tab_x1;
  5980. }
  5981. else if (tab_bar->ScrollingTarget < tab_x2 - tab_bar->BarRect.GetWidth())
  5982. {
  5983. tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - tab_bar->BarRect.GetWidth()) - tab_bar->ScrollingAnim, 0.0f);
  5984. tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();
  5985. }
  5986. }
  5987. void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
  5988. {
  5989. IM_ASSERT(dir == -1 || dir == +1);
  5990. IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
  5991. tab_bar->ReorderRequestTabId = tab->ID;
  5992. tab_bar->ReorderRequestDir = (ImS8)dir;
  5993. }
  5994. static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
  5995. {
  5996. ImGuiContext& g = *GImGui;
  5997. ImGuiWindow* window = g.CurrentWindow;
  5998. const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
  5999. const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
  6000. const ImVec2 backup_cursor_pos = window->DC.CursorPos;
  6001. //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
  6002. const ImRect avail_bar_rect = tab_bar->BarRect;
  6003. bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));
  6004. if (want_clip_rect)
  6005. PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);
  6006. ImGuiTabItem* tab_to_select = NULL;
  6007. int select_dir = 0;
  6008. ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
  6009. arrow_col.w *= 0.5f;
  6010. PushStyleColor(ImGuiCol_Text, arrow_col);
  6011. PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
  6012. const float backup_repeat_delay = g.IO.KeyRepeatDelay;
  6013. const float backup_repeat_rate = g.IO.KeyRepeatRate;
  6014. g.IO.KeyRepeatDelay = 0.250f;
  6015. g.IO.KeyRepeatRate = 0.200f;
  6016. window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);
  6017. if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
  6018. select_dir = -1;
  6019. window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);
  6020. if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
  6021. select_dir = +1;
  6022. PopStyleColor(2);
  6023. g.IO.KeyRepeatRate = backup_repeat_rate;
  6024. g.IO.KeyRepeatDelay = backup_repeat_delay;
  6025. if (want_clip_rect)
  6026. PopClipRect();
  6027. if (select_dir != 0)
  6028. if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
  6029. {
  6030. int selected_order = tab_bar->GetTabOrder(tab_item);
  6031. int target_order = selected_order + select_dir;
  6032. tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible
  6033. }
  6034. window->DC.CursorPos = backup_cursor_pos;
  6035. tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
  6036. return tab_to_select;
  6037. }
  6038. static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
  6039. {
  6040. ImGuiContext& g = *GImGui;
  6041. ImGuiWindow* window = g.CurrentWindow;
  6042. // We use g.Style.FramePadding.y to match the square ArrowButton size
  6043. const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
  6044. const ImVec2 backup_cursor_pos = window->DC.CursorPos;
  6045. window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
  6046. tab_bar->BarRect.Min.x += tab_list_popup_button_width;
  6047. ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
  6048. arrow_col.w *= 0.5f;
  6049. PushStyleColor(ImGuiCol_Text, arrow_col);
  6050. PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
  6051. bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview);
  6052. PopStyleColor(2);
  6053. ImGuiTabItem* tab_to_select = NULL;
  6054. if (open)
  6055. {
  6056. for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
  6057. {
  6058. ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
  6059. const char* tab_name = tab_bar->GetTabName(tab);
  6060. if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
  6061. tab_to_select = tab;
  6062. }
  6063. EndCombo();
  6064. }
  6065. window->DC.CursorPos = backup_cursor_pos;
  6066. return tab_to_select;
  6067. }
  6068. //-------------------------------------------------------------------------
  6069. // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
  6070. //-------------------------------------------------------------------------
  6071. // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
  6072. // and some of the construct which are not used in Master may be left here to facilitate merging.
  6073. //-------------------------------------------------------------------------
  6074. // - BeginTabItem()
  6075. // - EndTabItem()
  6076. // - TabItemEx() [Internal]
  6077. // - SetTabItemClosed()
  6078. // - TabItemCalcSize() [Internal]
  6079. // - TabItemBackground() [Internal]
  6080. // - TabItemLabelAndCloseButton() [Internal]
  6081. //-------------------------------------------------------------------------
  6082. bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
  6083. {
  6084. ImGuiContext& g = *GImGui;
  6085. ImGuiWindow* window = g.CurrentWindow;
  6086. if (window->SkipItems)
  6087. return false;
  6088. ImGuiTabBar* tab_bar = g.CurrentTabBar;
  6089. if (tab_bar == NULL)
  6090. {
  6091. IM_ASSERT_USER_ERROR(tab_bar, "BeginTabItem() Needs to be called between BeginTabBar() and EndTabBar()!");
  6092. return false;
  6093. }
  6094. bool ret = TabItemEx(tab_bar, label, p_open, flags);
  6095. if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
  6096. {
  6097. ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
  6098. PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
  6099. }
  6100. return ret;
  6101. }
  6102. void ImGui::EndTabItem()
  6103. {
  6104. ImGuiContext& g = *GImGui;
  6105. ImGuiWindow* window = g.CurrentWindow;
  6106. if (window->SkipItems)
  6107. return;
  6108. ImGuiTabBar* tab_bar = g.CurrentTabBar;
  6109. if (tab_bar == NULL)
  6110. {
  6111. IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!");
  6112. return;
  6113. }
  6114. IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
  6115. ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
  6116. if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
  6117. window->IDStack.pop_back();
  6118. }
  6119. bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
  6120. {
  6121. // Layout whole tab bar if not already done
  6122. if (tab_bar->WantLayout)
  6123. TabBarLayout(tab_bar);
  6124. ImGuiContext& g = *GImGui;
  6125. ImGuiWindow* window = g.CurrentWindow;
  6126. if (window->SkipItems)
  6127. return false;
  6128. const ImGuiStyle& style = g.Style;
  6129. const ImGuiID id = TabBarCalcTabID(tab_bar, label);
  6130. // If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
  6131. if (p_open && !*p_open)
  6132. {
  6133. PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
  6134. ItemAdd(ImRect(), id);
  6135. PopItemFlag();
  6136. return false;
  6137. }
  6138. // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
  6139. if (flags & ImGuiTabItemFlags_NoCloseButton)
  6140. p_open = NULL;
  6141. else if (p_open == NULL)
  6142. flags |= ImGuiTabItemFlags_NoCloseButton;
  6143. // Calculate tab contents size
  6144. ImVec2 size = TabItemCalcSize(label, p_open != NULL);
  6145. // Acquire tab data
  6146. ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
  6147. bool tab_is_new = false;
  6148. if (tab == NULL)
  6149. {
  6150. tab_bar->Tabs.push_back(ImGuiTabItem());
  6151. tab = &tab_bar->Tabs.back();
  6152. tab->ID = id;
  6153. tab->Width = size.x;
  6154. tab_is_new = true;
  6155. }
  6156. tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);
  6157. tab->ContentWidth = size.x;
  6158. const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
  6159. const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
  6160. const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
  6161. tab->LastFrameVisible = g.FrameCount;
  6162. tab->Flags = flags;
  6163. // Append name with zero-terminator
  6164. tab->NameOffset = tab_bar->TabsNames.size();
  6165. tab_bar->TabsNames.append(label, label + strlen(label) + 1);
  6166. // If we are not reorderable, always reset offset based on submission order.
  6167. // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
  6168. if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
  6169. {
  6170. tab->Offset = tab_bar->OffsetNextTab;
  6171. tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;
  6172. }
  6173. // Update selected tab
  6174. if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
  6175. if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
  6176. tab_bar->NextSelectedTabId = id; // New tabs gets activated
  6177. if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // SetSelected can only be passed on explicit tab bar
  6178. tab_bar->NextSelectedTabId = id;
  6179. // Lock visibility
  6180. bool tab_contents_visible = (tab_bar->VisibleTabId == id);
  6181. if (tab_contents_visible)
  6182. tab_bar->VisibleTabWasSubmitted = true;
  6183. // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
  6184. if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
  6185. if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
  6186. tab_contents_visible = true;
  6187. if (tab_appearing && !(tab_bar_appearing && !tab_is_new))
  6188. {
  6189. PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
  6190. ItemAdd(ImRect(), id);
  6191. PopItemFlag();
  6192. return tab_contents_visible;
  6193. }
  6194. if (tab_bar->SelectedTabId == id)
  6195. tab->LastFrameSelected = g.FrameCount;
  6196. // Backup current layout position
  6197. const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
  6198. // Layout
  6199. size.x = tab->Width;
  6200. window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
  6201. ImVec2 pos = window->DC.CursorPos;
  6202. ImRect bb(pos, pos + size);
  6203. // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
  6204. bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x > tab_bar->BarRect.Max.x);
  6205. if (want_clip_rect)
  6206. PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true);
  6207. ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
  6208. ItemSize(bb.GetSize(), style.FramePadding.y);
  6209. window->DC.CursorMaxPos = backup_cursor_max_pos;
  6210. if (!ItemAdd(bb, id))
  6211. {
  6212. if (want_clip_rect)
  6213. PopClipRect();
  6214. window->DC.CursorPos = backup_main_cursor_pos;
  6215. return tab_contents_visible;
  6216. }
  6217. // Click to Select a tab
  6218. ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);
  6219. if (g.DragDropActive)
  6220. button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
  6221. bool hovered, held;
  6222. bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
  6223. if (pressed)
  6224. tab_bar->NextSelectedTabId = id;
  6225. hovered |= (g.HoveredId == id);
  6226. // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
  6227. if (!held)
  6228. SetItemAllowOverlap();
  6229. // Drag and drop: re-order tabs
  6230. if (held && !tab_appearing && IsMouseDragging(0))
  6231. {
  6232. if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
  6233. {
  6234. // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
  6235. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
  6236. {
  6237. if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
  6238. TabBarQueueChangeTabOrder(tab_bar, tab, -1);
  6239. }
  6240. else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
  6241. {
  6242. if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
  6243. TabBarQueueChangeTabOrder(tab_bar, tab, +1);
  6244. }
  6245. }
  6246. }
  6247. #if 0
  6248. if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->ContentWidth)
  6249. {
  6250. // Enlarge tab display when hovering
  6251. bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
  6252. display_draw_list = GetForegroundDrawList(window);
  6253. TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
  6254. }
  6255. #endif
  6256. // Render tab shape
  6257. ImDrawList* display_draw_list = window->DrawList;
  6258. const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
  6259. TabItemBackground(display_draw_list, bb, flags, tab_col);
  6260. RenderNavHighlight(bb, id);
  6261. // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
  6262. const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
  6263. if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
  6264. tab_bar->NextSelectedTabId = id;
  6265. if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
  6266. flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
  6267. // Render tab label, process close button
  6268. const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0;
  6269. bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id);
  6270. if (just_closed && p_open != NULL)
  6271. {
  6272. *p_open = false;
  6273. TabBarCloseTab(tab_bar, tab);
  6274. }
  6275. // Restore main window position so user can draw there
  6276. if (want_clip_rect)
  6277. PopClipRect();
  6278. window->DC.CursorPos = backup_main_cursor_pos;
  6279. // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
  6280. // We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar (which g.HoveredId ignores)
  6281. if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f && IsItemHovered())
  6282. if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip))
  6283. SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
  6284. return tab_contents_visible;
  6285. }
  6286. // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
  6287. // To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
  6288. void ImGui::SetTabItemClosed(const char* label)
  6289. {
  6290. ImGuiContext& g = *GImGui;
  6291. bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
  6292. if (is_within_manual_tab_bar)
  6293. {
  6294. ImGuiTabBar* tab_bar = g.CurrentTabBar;
  6295. IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
  6296. ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
  6297. TabBarRemoveTab(tab_bar, tab_id);
  6298. }
  6299. }
  6300. ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
  6301. {
  6302. ImGuiContext& g = *GImGui;
  6303. ImVec2 label_size = CalcTextSize(label, NULL, true);
  6304. ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
  6305. if (has_close_button)
  6306. size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
  6307. else
  6308. size.x += g.Style.FramePadding.x + 1.0f;
  6309. return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
  6310. }
  6311. void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
  6312. {
  6313. // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
  6314. ImGuiContext& g = *GImGui;
  6315. const float width = bb.GetWidth();
  6316. IM_UNUSED(flags);
  6317. IM_ASSERT(width > 0.0f);
  6318. const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));
  6319. const float y1 = bb.Min.y + 1.0f;
  6320. const float y2 = bb.Max.y - 1.0f;
  6321. draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
  6322. draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
  6323. draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
  6324. draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
  6325. draw_list->PathFillConvex(col);
  6326. if (g.Style.TabBorderSize > 0.0f)
  6327. {
  6328. draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
  6329. draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
  6330. draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
  6331. draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
  6332. draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);
  6333. }
  6334. }
  6335. // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
  6336. // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
  6337. bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id)
  6338. {
  6339. ImGuiContext& g = *GImGui;
  6340. ImVec2 label_size = CalcTextSize(label, NULL, true);
  6341. if (bb.GetWidth() <= 1.0f)
  6342. return false;
  6343. // Render text label (with clipping + alpha gradient) + unsaved marker
  6344. const char* TAB_UNSAVED_MARKER = "*";
  6345. ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
  6346. if (flags & ImGuiTabItemFlags_UnsavedDocument)
  6347. {
  6348. text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
  6349. ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + IM_FLOOR(-g.FontSize * 0.25f));
  6350. RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
  6351. }
  6352. ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
  6353. // Close Button
  6354. // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
  6355. // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
  6356. // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
  6357. // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
  6358. bool close_button_pressed = false;
  6359. bool close_button_visible = false;
  6360. if (close_button_id != 0)
  6361. if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)
  6362. close_button_visible = true;
  6363. if (close_button_visible)
  6364. {
  6365. ImGuiItemHoveredDataBackup last_item_backup;
  6366. const float close_button_sz = g.FontSize;
  6367. PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding);
  6368. if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x * 2.0f - close_button_sz, bb.Min.y)))
  6369. close_button_pressed = true;
  6370. PopStyleVar();
  6371. last_item_backup.Restore();
  6372. // Close with middle mouse button
  6373. if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
  6374. close_button_pressed = true;
  6375. text_pixel_clip_bb.Max.x -= close_button_sz;
  6376. }
  6377. float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
  6378. RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
  6379. return close_button_pressed;
  6380. }
  6381. //-------------------------------------------------------------------------
  6382. // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
  6383. // In the current version, Columns are very weak. Needs to be replaced with a more full-featured system.
  6384. //-------------------------------------------------------------------------
  6385. // - GetColumnIndex()
  6386. // - GetColumnCount()
  6387. // - GetColumnOffset()
  6388. // - GetColumnWidth()
  6389. // - SetColumnOffset()
  6390. // - SetColumnWidth()
  6391. // - PushColumnClipRect() [Internal]
  6392. // - PushColumnsBackground() [Internal]
  6393. // - PopColumnsBackground() [Internal]
  6394. // - FindOrCreateColumns() [Internal]
  6395. // - GetColumnsID() [Internal]
  6396. // - BeginColumns()
  6397. // - NextColumn()
  6398. // - EndColumns()
  6399. // - Columns()
  6400. //-------------------------------------------------------------------------
  6401. int ImGui::GetColumnIndex()
  6402. {
  6403. ImGuiWindow* window = GetCurrentWindowRead();
  6404. return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0;
  6405. }
  6406. int ImGui::GetColumnsCount()
  6407. {
  6408. ImGuiWindow* window = GetCurrentWindowRead();
  6409. return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1;
  6410. }
  6411. float ImGui::GetColumnOffsetFromNorm(const ImGuiColumns* columns, float offset_norm)
  6412. {
  6413. return offset_norm * (columns->OffMaxX - columns->OffMinX);
  6414. }
  6415. float ImGui::GetColumnNormFromOffset(const ImGuiColumns* columns, float offset)
  6416. {
  6417. return offset / (columns->OffMaxX - columns->OffMinX);
  6418. }
  6419. static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f;
  6420. static float GetDraggedColumnOffset(ImGuiColumns* columns, int column_index)
  6421. {
  6422. // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
  6423. // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
  6424. ImGuiContext& g = *GImGui;
  6425. ImGuiWindow* window = g.CurrentWindow;
  6426. IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
  6427. IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
  6428. float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + COLUMNS_HIT_RECT_HALF_WIDTH - window->Pos.x;
  6429. x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
  6430. if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
  6431. x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
  6432. return x;
  6433. }
  6434. float ImGui::GetColumnOffset(int column_index)
  6435. {
  6436. ImGuiWindow* window = GetCurrentWindowRead();
  6437. ImGuiColumns* columns = window->DC.CurrentColumns;
  6438. if (columns == NULL)
  6439. return 0.0f;
  6440. if (column_index < 0)
  6441. column_index = columns->Current;
  6442. IM_ASSERT(column_index < columns->Columns.Size);
  6443. const float t = columns->Columns[column_index].OffsetNorm;
  6444. const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t);
  6445. return x_offset;
  6446. }
  6447. static float GetColumnWidthEx(ImGuiColumns* columns, int column_index, bool before_resize = false)
  6448. {
  6449. if (column_index < 0)
  6450. column_index = columns->Current;
  6451. float offset_norm;
  6452. if (before_resize)
  6453. offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
  6454. else
  6455. offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
  6456. return ImGui::GetColumnOffsetFromNorm(columns, offset_norm);
  6457. }
  6458. float ImGui::GetColumnWidth(int column_index)
  6459. {
  6460. ImGuiContext& g = *GImGui;
  6461. ImGuiWindow* window = g.CurrentWindow;
  6462. ImGuiColumns* columns = window->DC.CurrentColumns;
  6463. if (columns == NULL)
  6464. return GetContentRegionAvail().x;
  6465. if (column_index < 0)
  6466. column_index = columns->Current;
  6467. return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
  6468. }
  6469. void ImGui::SetColumnOffset(int column_index, float offset)
  6470. {
  6471. ImGuiContext& g = *GImGui;
  6472. ImGuiWindow* window = g.CurrentWindow;
  6473. ImGuiColumns* columns = window->DC.CurrentColumns;
  6474. IM_ASSERT(columns != NULL);
  6475. if (column_index < 0)
  6476. column_index = columns->Current;
  6477. IM_ASSERT(column_index < columns->Columns.Size);
  6478. const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1);
  6479. const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
  6480. if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
  6481. offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
  6482. columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX);
  6483. if (preserve_width)
  6484. SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
  6485. }
  6486. void ImGui::SetColumnWidth(int column_index, float width)
  6487. {
  6488. ImGuiWindow* window = GetCurrentWindowRead();
  6489. ImGuiColumns* columns = window->DC.CurrentColumns;
  6490. IM_ASSERT(columns != NULL);
  6491. if (column_index < 0)
  6492. column_index = columns->Current;
  6493. SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
  6494. }
  6495. void ImGui::PushColumnClipRect(int column_index)
  6496. {
  6497. ImGuiWindow* window = GetCurrentWindowRead();
  6498. ImGuiColumns* columns = window->DC.CurrentColumns;
  6499. if (column_index < 0)
  6500. column_index = columns->Current;
  6501. ImGuiColumnData* column = &columns->Columns[column_index];
  6502. PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false);
  6503. }
  6504. // Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns)
  6505. void ImGui::PushColumnsBackground()
  6506. {
  6507. ImGuiWindow* window = GetCurrentWindowRead();
  6508. ImGuiColumns* columns = window->DC.CurrentColumns;
  6509. if (columns->Count == 1)
  6510. return;
  6511. window->DrawList->ChannelsSetCurrent(0);
  6512. int cmd_size = window->DrawList->CmdBuffer.Size;
  6513. PushClipRect(columns->HostClipRect.Min, columns->HostClipRect.Max, false);
  6514. IM_UNUSED(cmd_size);
  6515. IM_ASSERT(cmd_size == window->DrawList->CmdBuffer.Size); // Being in channel 0 this should not have created an ImDrawCmd
  6516. }
  6517. void ImGui::PopColumnsBackground()
  6518. {
  6519. ImGuiWindow* window = GetCurrentWindowRead();
  6520. ImGuiColumns* columns = window->DC.CurrentColumns;
  6521. if (columns->Count == 1)
  6522. return;
  6523. window->DrawList->ChannelsSetCurrent(columns->Current + 1);
  6524. PopClipRect();
  6525. }
  6526. ImGuiColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id)
  6527. {
  6528. // We have few columns per window so for now we don't need bother much with turning this into a faster lookup.
  6529. for (int n = 0; n < window->ColumnsStorage.Size; n++)
  6530. if (window->ColumnsStorage[n].ID == id)
  6531. return &window->ColumnsStorage[n];
  6532. window->ColumnsStorage.push_back(ImGuiColumns());
  6533. ImGuiColumns* columns = &window->ColumnsStorage.back();
  6534. columns->ID = id;
  6535. return columns;
  6536. }
  6537. ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count)
  6538. {
  6539. ImGuiWindow* window = GetCurrentWindow();
  6540. // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
  6541. // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
  6542. PushID(0x11223347 + (str_id ? 0 : columns_count));
  6543. ImGuiID id = window->GetID(str_id ? str_id : "columns");
  6544. PopID();
  6545. return id;
  6546. }
  6547. void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
  6548. {
  6549. ImGuiContext& g = *GImGui;
  6550. ImGuiWindow* window = GetCurrentWindow();
  6551. IM_ASSERT(columns_count >= 1 && columns_count <= 64); // Maximum 64 columns
  6552. IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported
  6553. // Acquire storage for the columns set
  6554. ImGuiID id = GetColumnsID(str_id, columns_count);
  6555. ImGuiColumns* columns = FindOrCreateColumns(window, id);
  6556. IM_ASSERT(columns->ID == id);
  6557. columns->Current = 0;
  6558. columns->Count = columns_count;
  6559. columns->Flags = flags;
  6560. window->DC.CurrentColumns = columns;
  6561. columns->HostCursorPosY = window->DC.CursorPos.y;
  6562. columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x;
  6563. columns->HostClipRect = window->ClipRect;
  6564. columns->HostWorkRect = window->WorkRect;
  6565. // Set state for first column
  6566. // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect
  6567. const float column_padding = g.Style.ItemSpacing.x;
  6568. const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize));
  6569. const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f);
  6570. const float max_2 = window->WorkRect.Max.x + half_clip_extend_x;
  6571. columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f);
  6572. columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f);
  6573. columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
  6574. // Clear data if columns count changed
  6575. if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
  6576. columns->Columns.resize(0);
  6577. // Initialize default widths
  6578. columns->IsFirstFrame = (columns->Columns.Size == 0);
  6579. if (columns->Columns.Size == 0)
  6580. {
  6581. columns->Columns.reserve(columns_count + 1);
  6582. for (int n = 0; n < columns_count + 1; n++)
  6583. {
  6584. ImGuiColumnData column;
  6585. column.OffsetNorm = n / (float)columns_count;
  6586. columns->Columns.push_back(column);
  6587. }
  6588. }
  6589. for (int n = 0; n < columns_count; n++)
  6590. {
  6591. // Compute clipping rectangle
  6592. ImGuiColumnData* column = &columns->Columns[n];
  6593. float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n));
  6594. float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
  6595. column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
  6596. column->ClipRect.ClipWith(window->ClipRect);
  6597. }
  6598. if (columns->Count > 1)
  6599. {
  6600. window->DrawList->ChannelsSplit(1 + columns->Count);
  6601. window->DrawList->ChannelsSetCurrent(1);
  6602. PushColumnClipRect(0);
  6603. }
  6604. // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user.
  6605. float offset_0 = GetColumnOffset(columns->Current);
  6606. float offset_1 = GetColumnOffset(columns->Current + 1);
  6607. float width = offset_1 - offset_0;
  6608. PushItemWidth(width * 0.65f);
  6609. window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
  6610. window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
  6611. window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
  6612. }
  6613. void ImGui::NextColumn()
  6614. {
  6615. ImGuiWindow* window = GetCurrentWindow();
  6616. if (window->SkipItems || window->DC.CurrentColumns == NULL)
  6617. return;
  6618. ImGuiContext& g = *GImGui;
  6619. ImGuiColumns* columns = window->DC.CurrentColumns;
  6620. if (columns->Count == 1)
  6621. {
  6622. window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
  6623. IM_ASSERT(columns->Current == 0);
  6624. return;
  6625. }
  6626. PopItemWidth();
  6627. PopClipRect();
  6628. const float column_padding = g.Style.ItemSpacing.x;
  6629. columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
  6630. if (++columns->Current < columns->Count)
  6631. {
  6632. // Columns 1+ ignore IndentX (by canceling it out)
  6633. // FIXME-COLUMNS: Unnecessary, could be locked?
  6634. window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding;
  6635. window->DrawList->ChannelsSetCurrent(columns->Current + 1);
  6636. }
  6637. else
  6638. {
  6639. // New row/line
  6640. // Column 0 honor IndentX
  6641. window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
  6642. window->DrawList->ChannelsSetCurrent(1);
  6643. columns->Current = 0;
  6644. columns->LineMinY = columns->LineMaxY;
  6645. }
  6646. window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
  6647. window->DC.CursorPos.y = columns->LineMinY;
  6648. window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
  6649. window->DC.CurrLineTextBaseOffset = 0.0f;
  6650. PushColumnClipRect(columns->Current); // FIXME-COLUMNS: Could it be an overwrite?
  6651. // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup.
  6652. float offset_0 = GetColumnOffset(columns->Current);
  6653. float offset_1 = GetColumnOffset(columns->Current + 1);
  6654. float width = offset_1 - offset_0;
  6655. PushItemWidth(width * 0.65f);
  6656. window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;
  6657. }
  6658. void ImGui::EndColumns()
  6659. {
  6660. ImGuiContext& g = *GImGui;
  6661. ImGuiWindow* window = GetCurrentWindow();
  6662. ImGuiColumns* columns = window->DC.CurrentColumns;
  6663. IM_ASSERT(columns != NULL);
  6664. PopItemWidth();
  6665. if (columns->Count > 1)
  6666. {
  6667. PopClipRect();
  6668. window->DrawList->ChannelsMerge();
  6669. }
  6670. const ImGuiColumnsFlags flags = columns->Flags;
  6671. columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
  6672. window->DC.CursorPos.y = columns->LineMaxY;
  6673. if (!(flags & ImGuiColumnsFlags_GrowParentContentsSize))
  6674. window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent
  6675. // Draw columns borders and handle resize
  6676. // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy
  6677. bool is_being_resized = false;
  6678. if (!(flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
  6679. {
  6680. // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.
  6681. const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y);
  6682. const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y);
  6683. int dragging_column = -1;
  6684. for (int n = 1; n < columns->Count; n++)
  6685. {
  6686. ImGuiColumnData* column = &columns->Columns[n];
  6687. float x = window->Pos.x + GetColumnOffset(n);
  6688. const ImGuiID column_id = columns->ID + ImGuiID(n);
  6689. const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH;
  6690. const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2));
  6691. KeepAliveID(column_id);
  6692. if (IsClippedEx(column_hit_rect, column_id, false))
  6693. continue;
  6694. bool hovered = false, held = false;
  6695. if (!(flags & ImGuiColumnsFlags_NoResize))
  6696. {
  6697. ButtonBehavior(column_hit_rect, column_id, &hovered, &held);
  6698. if (hovered || held)
  6699. g.MouseCursor = ImGuiMouseCursor_ResizeEW;
  6700. if (held && !(column->Flags & ImGuiColumnsFlags_NoResize))
  6701. dragging_column = n;
  6702. }
  6703. // Draw column
  6704. const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
  6705. const float xi = IM_FLOOR(x);
  6706. window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);
  6707. }
  6708. // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
  6709. if (dragging_column != -1)
  6710. {
  6711. if (!columns->IsBeingResized)
  6712. for (int n = 0; n < columns->Count + 1; n++)
  6713. columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
  6714. columns->IsBeingResized = is_being_resized = true;
  6715. float x = GetDraggedColumnOffset(columns, dragging_column);
  6716. SetColumnOffset(dragging_column, x);
  6717. }
  6718. }
  6719. columns->IsBeingResized = is_being_resized;
  6720. window->WorkRect = columns->HostWorkRect;
  6721. window->DC.CurrentColumns = NULL;
  6722. window->DC.ColumnsOffset.x = 0.0f;
  6723. window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
  6724. }
  6725. // [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
  6726. void ImGui::Columns(int columns_count, const char* id, bool border)
  6727. {
  6728. ImGuiWindow* window = GetCurrentWindow();
  6729. IM_ASSERT(columns_count >= 1);
  6730. ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
  6731. //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
  6732. ImGuiColumns* columns = window->DC.CurrentColumns;
  6733. if (columns != NULL && columns->Count == columns_count && columns->Flags == flags)
  6734. return;
  6735. if (columns != NULL)
  6736. EndColumns();
  6737. if (columns_count != 1)
  6738. BeginColumns(id, columns_count, flags);
  6739. }
  6740. //-------------------------------------------------------------------------