UI Moderne avec les Canvas

Partagez votre expérience de PureBasic avec les autres utilisateurs.
Avatar de l’utilisateur
MetalOS
Messages : 1509
Inscription : mar. 20/juin/2006 22:17
Localisation : Lorraine
Contact :

UI Moderne avec les Canvas

Message par MetalOS »

Salut les gars, après plusieurs année à prendre des petits bout de code par si par là pour essayer avec la librairie Canvas de PB de faire une interface plus moderne pour mes logiciels. Je vous partage mes recherches si ca peut aider quelqu'un. Je ne vous cache pas que si ca a été possible pour moi c'est aussi parce que j'ai utilisé TchatGPT pour qu'il m'aide à trouver certains blocage que je rencontrais.

Il reste beaucoup d'améliorations et de codage à faire avant d'avoir une interface très fonctionnel mais ca montre les possibilitées de la lib Canvas pour faire des interfaces un peut plus pro.

Le code devrait être Cross platforme car ca n'utilise que les commandes natif de PB.

Code : Tout sélectionner

; ============================================================================
; By Metalos
; UI avec Canvas + petits Canvas-boutons
; chaque zone = ContainerGadget dans les splitters.
; PureBasic 6.x (macOS/Win/Linux)
; ============================================================================

EnableExplicit

;============================= IDs =====================================
#WIN_MAIN = 0

; Containers mis dans les splitters
#CNT_Header   = 10
#CNT_Left     = 11
#CNT_Viewport = 12
#CNT_Right    = 13
#CNT_Timeline = 14

; Canvas de fond (dessin)
#CV_Header    = 20
#CV_Left      = 21
#CV_Viewport  = 22
#CV_Right     = 23
#CV_Timeline  = 24

; Splitters
#SP_CenterRight = 30
#SP_LeftCenter  = 31
#SP_MainBottom  = 32

; Boutons (Canvas) — enfants des containers correspondants
; Header
#BTN_File   = 100
#BTN_Edit   = 101
#BTN_Render = 102
#BTN_Window = 103
#BTN_Help   = 104

; Left panel
#BTN_AddObj = 110
#BTN_DelObj = 111

; Viewport
#BTN_ViewFront = 120
#BTN_ViewTop   = 121
#BTN_ViewSide  = 122
#BTN_ViewPersp = 123
#BTN_ZoomFit   = 124

; Right panel tabs
#BTN_TabRender    = 130
#BTN_TabScene     = 131
#BTN_TabWorld     = 132
#BTN_TabObject    = 133
#BTN_TabModifiers = 134

; Timeline
#BTN_Stop  = 140
#BTN_Play  = 141
#BTN_Prev  = 142
#BTN_Next  = 143
#BTN_Loop  = 144

; Timer
#TMR_Play = 1

;============================= State ===================================
Global zoom.f = 1.0
Global gridSize = 32
Global camW = 640, camH = 360

Global playing = #False
Global frameStart = 1, frameEnd = 462
Global currentFrame = 1
Global dragTimeline = #False

;============================= Theme ===================================
Structure ColorSet
  bg.i : mid.i : panel.i : panel2.i : accent.i : line.i : text.i : textDim.i
EndStructure

Global theme.ColorSet
theme\bg     = $1E1E1E
theme\mid    = $262626
theme\panel  = $2E2E2E
theme\panel2 = $353535
theme\accent = $FFAA00
theme\line   = $404040
theme\text   = $EAEAEA
theme\textDim= $B0B0B0

;============================= Helpers =================================
Procedure.i ClampI(v.i, a.i, b.i)
  If v < a : ProcedureReturn a : EndIf
  If v > b : ProcedureReturn b : EndIf
  ProcedureReturn v
EndProcedure

Procedure.f ClampF(v.f, a.f, b.f)
  If v < a : ProcedureReturn a : EndIf
  If v > b : ProcedureReturn b : EndIf
  ProcedureReturn v
EndProcedure

Procedure DrawButton(id, txt$)
  If StartDrawing(CanvasOutput(id))
    Box(0,0,OutputWidth(),OutputHeight(), $3A3A3A)
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawingFont(FontID(0))
    DrawText((OutputWidth()-TextWidth(txt$))/2, (OutputHeight()-TextHeight(txt$))/2, txt$, theme\text)
    StopDrawing()
  EndIf
EndProcedure

;============================= Drawing (fond des zones) =================
Procedure DrawHeader()
  If StartDrawing(CanvasOutput(#CV_Header))
    Box(0,0,OutputWidth(),OutputHeight(), theme\mid)
    Box(0,0,OutputWidth(),2, theme\accent)
    DrawingFont(FontID(0)) : DrawingMode(#PB_2DDrawing_Transparent)
    Define s$ = "Ve:136 | Fa:93 | Ob:40-1 | La:16 | Mem:241.63M"
    DrawText(OutputWidth()-TextWidth(s$)-12,7,s$, theme\textDim)
    StopDrawing()
  EndIf
EndProcedure

Procedure DrawLeft()
  If StartDrawing(CanvasOutput(#CV_Left))
    Box(0,0,OutputWidth(),OutputHeight(), theme\panel)
    Box(0,0,OutputWidth(),24, theme\panel2)
    DrawingFont(FontID(0)) : DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(8,5,"Outliner", theme\text)
    Define y = 30, i
    For i=0 To 50
      If y > OutputHeight()-18 : Break : EndIf
      DrawText(10,y,"• Empty_"+RSet(Str(i),2,"0"), theme\textDim)
      y + 16
    Next
    Box(OutputWidth()-1,0,1,OutputHeight(), theme\line)
    StopDrawing()
  EndIf
EndProcedure

Procedure DrawViewport()
  If StartDrawing(CanvasOutput(#CV_Viewport))
    Box(0,0,OutputWidth(),OutputHeight(), theme\bg)
    DrawingFont(FontID(0))
    Define s = Round(gridSize*zoom,#PB_Round_Nearest)
    If s < 10 : s = 10 : EndIf
    Define x, y
    x = 0 : While x <= OutputWidth() : Line(x,0,0,OutputHeight(), theme\line) : x + s : Wend
    y = 0 : While y <= OutputHeight() : Line(0,y,OutputWidth(),0, theme\line) : y + s : Wend
    Define w = camW*zoom, h = camH*zoom
    If w>OutputWidth()-20 : w = OutputWidth()-20 : EndIf
    If h>OutputHeight()-20 : h = OutputHeight()-20 : EndIf
    Define cx = OutputWidth()/2 - w/2
    Define cy = OutputHeight()/2 - h/2
    Box(cx,cy,w,h, RGB(0,0,0))
    Box(cx+1,cy+1,w-2,h-2, theme\panel2)
    Box(cx+10,cy+10,w-20,h-20, theme\mid)
    Protected rx = w/3, ry = h/2
    If rx > 0 And ry > 0 : Ellipse(cx+w/2, cy+h/2, rx, ry, $5A5A5A) : EndIf
    If w > 60 And h > 60
      Ellipse(cx+w/2-30, cy+h/2-40, 18, 12, $FFFFFF)
      Ellipse(cx+w/2+30, cy+h/2-40, 18, 12, $FFFFFF)
      Ellipse(cx+w/2-30, cy+h/2-40, 6, 6, $000000)
      Ellipse(cx+w/2+30, cy+h/2-40, 6, 6, $000000)
    EndIf
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(8,6,"Camera Persp", theme\textDim)
    Box(0,0,OutputWidth(),1, theme\line)
    Box(OutputWidth()-1,0,1,OutputHeight(), theme\line)
    StopDrawing()
  EndIf
EndProcedure

Procedure DrawRight()
  If StartDrawing(CanvasOutput(#CV_Right))
    Box(0,0,OutputWidth(),OutputHeight(), theme\panel)
    Box(0,0,OutputWidth(),28, theme\panel2)
    DrawingFont(FontID(0)) : DrawingMode(#PB_2DDrawing_Transparent)
    Define y = 40, i
    For i=0 To 5
      Box(10,y,OutputWidth()-20,26, theme\mid)
      DrawText(18,y+6,"Section "+Str(i+1), theme\textDim)
      y + 36
      If y>OutputHeight()-36 : Break : EndIf
    Next
    Box(0,0,1,OutputHeight(), theme\line)
    StopDrawing()
  EndIf
EndProcedure

Procedure DrawTimeline()
  If StartDrawing(CanvasOutput(#CV_Timeline))
    Box(0,0,OutputWidth(),OutputHeight(), theme\panel)
    Box(0,0,OutputWidth(),22, theme\panel2)
    DrawingFont(FontID(0)) : DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(8,4,"Timeline", theme\text)
    Define top = 24, h = OutputHeight()-top-8
    Box(0,top,OutputWidth(),h, theme\mid)
    Define i, stepFrames = 10
    Define pxPerFrame.f = (OutputWidth()-20) / (frameEnd-frameStart+0.0)
    i = frameStart
    While i <= frameEnd
      Define x = 10 + (i-frameStart) * pxPerFrame
      Line(x, top, 0, 8, theme\line)
      DrawText(x-TextWidth(Str(i))/2, top+10, Str(i), theme\textDim)
      i + stepFrames
    Wend
    currentFrame = ClampI(currentFrame, frameStart, frameEnd)
    Define cx = 10 + (currentFrame-frameStart) * pxPerFrame
    Box(cx-1, top, 3, h, theme\accent)
    DrawText(OutputWidth()-120, 4, "Frame: "+Str(currentFrame), theme\text)
    StopDrawing()
  EndIf
EndProcedure

;============================= Callbacks (click-only) ==================
Macro ClickOnly()
  If EventType() <> #PB_EventType_LeftButtonDown : ProcedureReturn : EndIf
EndMacro

Procedure CB_File()      : ClickOnly() : Debug "File" : EndProcedure
Procedure CB_Edit()      : ClickOnly() : Debug "Edit" : EndProcedure
Procedure CB_Render()    : ClickOnly() : Debug "Render" : EndProcedure
Procedure CB_Window()    : ClickOnly() : Debug "Window" : EndProcedure
Procedure CB_Help()      : ClickOnly() : Debug "Help" : EndProcedure

Procedure CB_AddObj()    : ClickOnly() : Debug "+ Add object" : EndProcedure
Procedure CB_DelObj()    : ClickOnly() : Debug "- Delete object" : EndProcedure

Procedure CB_ViewFront() : ClickOnly() : Debug "View: Front" : EndProcedure
Procedure CB_ViewTop()   : ClickOnly() : Debug "View: Top" : EndProcedure
Procedure CB_ViewSide()  : ClickOnly() : Debug "View: Side" : EndProcedure
Procedure CB_ViewPersp() : ClickOnly() : Debug "View: Persp" : EndProcedure
Procedure CB_ZoomFit()   : ClickOnly() : Debug "Zoom to Fit" : EndProcedure

Procedure CB_TabRender()    : ClickOnly() : Debug "Tab: Render" : EndProcedure
Procedure CB_TabScene()     : ClickOnly() : Debug "Tab: Scene" : EndProcedure
Procedure CB_TabWorld()     : ClickOnly() : Debug "Tab: World" : EndProcedure
Procedure CB_TabObject()    : ClickOnly() : Debug "Tab: Object" : EndProcedure
Procedure CB_TabModifiers() : ClickOnly() : Debug "Tab: Modifiers" : EndProcedure

Procedure CB_Stop()
  ClickOnly()
  playing = #False
  RemoveWindowTimer(#WIN_MAIN, #TMR_Play)
  DrawTimeline()
  Debug "Stop"
EndProcedure

Procedure CB_Play()
  ClickOnly()
  playing ! 1
  If playing
    AddWindowTimer(#WIN_MAIN, #TMR_Play, 30)
    Debug "Play"
  Else
    RemoveWindowTimer(#WIN_MAIN, #TMR_Play)
    Debug "Pause"
  EndIf
  DrawTimeline()
EndProcedure

Procedure CB_Prev()
  ClickOnly()
  currentFrame - 1 : If currentFrame < frameStart : currentFrame = frameStart : EndIf
  DrawTimeline()
  Debug "Prev -> "+Str(currentFrame)
EndProcedure

Procedure CB_Next()
  ClickOnly()
  currentFrame + 1 : If currentFrame > frameEnd : currentFrame = frameEnd : EndIf
  DrawTimeline()
  Debug "Next -> "+Str(currentFrame)
EndProcedure

Procedure CB_Loop() : ClickOnly() : Debug "Loop toggle (fake)" : EndProcedure

;============================= Events des Canvas de fond ===============
Procedure HandleViewportEvent()
  Select EventType()
    Case #PB_EventType_MouseWheel
      Protected delta = GetGadgetAttribute(#CV_Viewport, #PB_Canvas_WheelDelta)
      If delta > 0 : zoom + 0.1 : Else : zoom - 0.1 : EndIf
      zoom = ClampF(zoom, 0.3, 3.0)
      DrawViewport()
    Case #PB_EventType_Resize
      ResizeGadget(#CV_Viewport, #PB_Ignore, #PB_Ignore, GadgetWidth(#CNT_Viewport), GadgetHeight(#CNT_Viewport))
      DrawViewport()
  EndSelect
EndProcedure

Procedure HandleTimelineEvent()
  Protected et = EventType()
  Protected mx = GetGadgetAttribute(#CV_Timeline, #PB_Canvas_MouseX)
  Protected my = GetGadgetAttribute(#CV_Timeline, #PB_Canvas_MouseY)
  Protected top = 24
  Protected pxPerFrame.f

  Select et
    Case #PB_EventType_LeftButtonDown
      If my >= top
        dragTimeline = #True
        pxPerFrame = (GadgetWidth(#CV_Timeline)-20) / (frameEnd-frameStart+0.0)
        currentFrame = frameStart + Int((mx-10) / pxPerFrame)
        DrawTimeline()
      EndIf
    Case #PB_EventType_LeftButtonUp
      dragTimeline = #False
    Case #PB_EventType_MouseMove
      If dragTimeline And my >= top
        pxPerFrame = (GadgetWidth(#CV_Timeline)-20) / (frameEnd-frameStart+0.0)
        currentFrame = frameStart + Int((mx-10) / pxPerFrame)
        DrawTimeline()
      EndIf
    Case #PB_EventType_Resize
      ResizeGadget(#CV_Timeline, #PB_Ignore, #PB_Ignore, GadgetWidth(#CNT_Timeline), GadgetHeight(#CNT_Timeline))
      DrawTimeline()
  EndSelect
EndProcedure

;============================= Layout ==================================
Procedure DrawAllBackgrounds()
  ResizeGadget(#CV_Header,   0, 0, GadgetWidth(#CNT_Header),   GadgetHeight(#CNT_Header))
  ResizeGadget(#CV_Left,     0, 0, GadgetWidth(#CNT_Left),     GadgetHeight(#CNT_Left))
  ResizeGadget(#CV_Viewport, 0, 0, GadgetWidth(#CNT_Viewport), GadgetHeight(#CNT_Viewport))
  ResizeGadget(#CV_Right,    0, 0, GadgetWidth(#CNT_Right),    GadgetHeight(#CNT_Right))
  ResizeGadget(#CV_Timeline, 0, 0, GadgetWidth(#CNT_Timeline), GadgetHeight(#CNT_Timeline))
  DrawHeader() : DrawLeft() : DrawViewport() : DrawRight() : DrawTimeline()
EndProcedure

Procedure ApplyInitialLayout()
  Protected w = WindowWidth(#WIN_MAIN)
  Protected h = WindowHeight(#WIN_MAIN)
  Protected headH = 28
  Protected leftW  = ClampI(Int(w * 0.23), 220, 420)
  Protected rightW = ClampI(Int(w * 0.26), 240, 520)
  Protected topH   = ClampI(h - headH - 140, 240, h - headH - 90)
  SetGadgetState(#SP_LeftCenter,  leftW)
  SetGadgetState(#SP_CenterRight, w - rightW)
  SetGadgetState(#SP_MainBottom,  topH)
  While WindowEvent() : Wend
  DrawAllBackgrounds()
EndProcedure

Procedure ResizeUI()
  Protected w = WindowWidth(#WIN_MAIN)
  Protected h = WindowHeight(#WIN_MAIN)
  ResizeGadget(#CNT_Header, 0,0, w, 28)
  ResizeGadget(#SP_MainBottom, 0,28, w, h-28)
  DrawAllBackgrounds()
EndProcedure

;============================= Main ====================================
If OpenWindow(#WIN_MAIN, 0,0, 1200, 730, "Blender-like UI (Canvas)", #PB_Window_SystemMenu|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)
  If LoadFont(0, "Arial", 10, #PB_Font_HighQuality)
    SetGadgetFont(#PB_Default, FontID(0))
  EndIf

  ; --- Containers hiérarchiques ---
  ContainerGadget(#CNT_Header,   0,0, 100,28, #PB_Container_Flat)
    CanvasGadget(#CV_Header, 0,0, 100,28, #PB_Canvas_Keyboard)
    CanvasGadget(#BTN_File,   10,4, 50,20) : DrawButton(#BTN_File,"File")     : BindGadgetEvent(#BTN_File,   @CB_File())
    CanvasGadget(#BTN_Edit,   64,4, 50,20) : DrawButton(#BTN_Edit,"Edit")     : BindGadgetEvent(#BTN_Edit,   @CB_Edit())
    CanvasGadget(#BTN_Render,118,4, 60,20) : DrawButton(#BTN_Render,"Render") : BindGadgetEvent(#BTN_Render, @CB_Render())
    CanvasGadget(#BTN_Window,184,4, 60,20) : DrawButton(#BTN_Window,"Window") : BindGadgetEvent(#BTN_Window, @CB_Window())
    CanvasGadget(#BTN_Help,  250,4, 50,20) : DrawButton(#BTN_Help,"Help")     : BindGadgetEvent(#BTN_Help,   @CB_Help())
  CloseGadgetList()

  ContainerGadget(#CNT_Left, 0,0, 260,100, #PB_Container_Flat)
    CanvasGadget(#CV_Left, 0,0, 260,100, #PB_Canvas_Keyboard)
    CanvasGadget(#BTN_AddObj, 10,30,80,22)  : DrawButton(#BTN_AddObj,"+ Add") : BindGadgetEvent(#BTN_AddObj,@CB_AddObj())
    CanvasGadget(#BTN_DelObj,100,30,80,22)  : DrawButton(#BTN_DelObj,"- Del") : BindGadgetEvent(#BTN_DelObj,@CB_DelObj())
  CloseGadgetList()

  ContainerGadget(#CNT_Viewport, 0,0, 640,100, #PB_Container_Flat)
    CanvasGadget(#CV_Viewport, 0,0, 640,100, #PB_Canvas_Keyboard)
    BindGadgetEvent(#CV_Viewport, @HandleViewportEvent())
    CanvasGadget(#BTN_ViewFront, 10,8,56,18)  : DrawButton(#BTN_ViewFront,"Front") : BindGadgetEvent(#BTN_ViewFront,@CB_ViewFront())
    CanvasGadget(#BTN_ViewTop,   70,8,56,18)  : DrawButton(#BTN_ViewTop,"Top")     : BindGadgetEvent(#BTN_ViewTop,  @CB_ViewTop())
    CanvasGadget(#BTN_ViewSide, 130,8,56,18)  : DrawButton(#BTN_ViewSide,"Side")   : BindGadgetEvent(#BTN_ViewSide, @CB_ViewSide())
    CanvasGadget(#BTN_ViewPersp,190,8,56,18)  : DrawButton(#BTN_ViewPersp,"Persp") : BindGadgetEvent(#BTN_ViewPersp,@CB_ViewPersp())
    CanvasGadget(#BTN_ZoomFit,  250,8,72,18)  : DrawButton(#BTN_ZoomFit,"ZoomFit") : BindGadgetEvent(#BTN_ZoomFit,  @CB_ZoomFit())
  CloseGadgetList()

  ContainerGadget(#CNT_Right, 0,0, 300,100, #PB_Container_Flat)
    CanvasGadget(#CV_Right, 0,0, 300,100, #PB_Canvas_Keyboard)
    CanvasGadget(#BTN_TabRender,   10,4,60,20)  : DrawButton(#BTN_TabRender,"Render")     : BindGadgetEvent(#BTN_TabRender,   @CB_TabRender())
    CanvasGadget(#BTN_TabScene,    74,4,60,20)  : DrawButton(#BTN_TabScene,"Scene")       : BindGadgetEvent(#BTN_TabScene,    @CB_TabScene())
    CanvasGadget(#BTN_TabWorld,   138,4,60,20)  : DrawButton(#BTN_TabWorld,"World")       : BindGadgetEvent(#BTN_TabWorld,    @CB_TabWorld())
    CanvasGadget(#BTN_TabObject,  202,4,60,20)  : DrawButton(#BTN_TabObject,"Object")     : BindGadgetEvent(#BTN_TabObject,   @CB_TabObject())
    CanvasGadget(#BTN_TabModifiers,266,4,76,20) : DrawButton(#BTN_TabModifiers,"Modifiers"): BindGadgetEvent(#BTN_TabModifiers,@CB_TabModifiers())
  CloseGadgetList()

  ContainerGadget(#CNT_Timeline, 0,0, 100,120, #PB_Container_Flat)
    CanvasGadget(#CV_Timeline, 0,0, 100,120, #PB_Canvas_Keyboard)
    BindGadgetEvent(#CV_Timeline, @HandleTimelineEvent())
    CanvasGadget(#BTN_Stop, 100,2,40,20) : DrawButton(#BTN_Stop,"Stop") : BindGadgetEvent(#BTN_Stop,@CB_Stop())
    CanvasGadget(#BTN_Play, 144,2,40,20) : DrawButton(#BTN_Play,"Play") : BindGadgetEvent(#BTN_Play,@CB_Play())
    CanvasGadget(#BTN_Prev, 188,2,40,20) : DrawButton(#BTN_Prev,"Prev") : BindGadgetEvent(#BTN_Prev,@CB_Prev())
    CanvasGadget(#BTN_Next, 232,2,40,20) : DrawButton(#BTN_Next,"Next") : BindGadgetEvent(#BTN_Next,@CB_Next())
    CanvasGadget(#BTN_Loop, 276,2,50,20) : DrawButton(#BTN_Loop,"Loop") : BindGadgetEvent(#BTN_Loop,@CB_Loop())
  CloseGadgetList()

  ; --- Splitters : contiennent les CONTAINERS du bloc central ---
  SplitterGadget(#SP_CenterRight, 0,0, 300,200, #CNT_Viewport, #CNT_Right, #PB_Splitter_Vertical|#PB_Splitter_Separator)
  SetGadgetAttribute(#SP_CenterRight, #PB_Splitter_FirstMinimumSize, 300)
  SetGadgetAttribute(#SP_CenterRight, #PB_Splitter_SecondMinimumSize, 220)

  SplitterGadget(#SP_LeftCenter,  0,0, 300,200, #CNT_Left, #SP_CenterRight, #PB_Splitter_Vertical|#PB_Splitter_Separator)
  SetGadgetAttribute(#SP_LeftCenter, #PB_Splitter_FirstMinimumSize, 200)

  ; ➜ CORRECTION: le splitter principal contient le bloc central en haut et la timeline en bas
  SplitterGadget(#SP_MainBottom,  0,0, 300,300, #SP_LeftCenter, #CNT_Timeline, #PB_Splitter_Separator)

  ; Positionner header hors splitter principal
  ResizeGadget(#CNT_Header, 0,0, WindowWidth(#WIN_MAIN), 28)
  ResizeGadget(#SP_MainBottom, 0,28, WindowWidth(#WIN_MAIN), WindowHeight(#WIN_MAIN)-28)

  ; Layout initial proportionnel + dessins
  ApplyInitialLayout()

  ; ====================== Event Loop =====================
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break

      Case #PB_Event_SizeWindow
        ResizeGadget(#CNT_Header, 0,0, WindowWidth(#WIN_MAIN), 28)
        ResizeGadget(#SP_MainBottom, 0,28, WindowWidth(#WIN_MAIN), WindowHeight(#WIN_MAIN)-28)
        DrawAllBackgrounds()

      Case #PB_Event_Gadget
        If EventType() = #PB_EventType_Change
          Select EventGadget()
            Case #SP_LeftCenter, #SP_CenterRight, #SP_MainBottom
              DrawAllBackgrounds()
          EndSelect
        EndIf

      Case #PB_Event_Timer
        If EventTimer() = #TMR_Play
          currentFrame + 1
          If currentFrame > frameEnd : currentFrame = frameStart : EndIf
          DrawTimeline()
        EndIf
    EndSelect
  ForEver
EndIf