local _, addon = ...
local SetGradient = addon.TransitionAPI.SetGradient;

local FadeFrame = NarciFadeUI.Fade;
local After = C_Timer.After;
local pi = math.pi;
local sqrt = math.sqrt;
local GetCursorPosition = GetCursorPosition;
local IsMouseButtonDown = IsMouseButtonDown;
local NarciAPI = NarciAPI;

local function EmptyFunc()
end

--TEMPS prefix: Deleted Later
local TEMPS = {};


local controllers = {};

local function AddControllerToWidget(widget)
    if not widget.controller then
        local controller = CreateFrame("Frame", nil, widget);
        controller:Hide();
        controller.t = 0;
        controller:SetScript("OnHide", function(self)
            self.t = 0;
            --print("Hide Controller");
        end);
        widget.controller = controller;
        tinsert(controllers, controller);
    end
    return widget.controller
end

--------------------------------------------------------------------------------------------------
--Name: Shimmer Button
--Type: Button
--Description: Shimmers slowly. Maintains highlight when mouseovered
--Notes: starting Alpha is always "0"

TEMPS.totalDuration = 0;
local shimmerStyle = {
    {toAlpha = 0.5, duration = 0.4},    --#1
    {toAlpha = 0.5, duration = 0.25},   --#2
    {toAlpha = 0, duration = 1.5},      --#3
    {toAlpha = 0, duration = 0.35},     --#4
};

--time accumulation
local timeAlpha = {
    --{totalDuration, toAlpha};
};
local TIER_SHIMMER = #shimmerStyle;

for i = 1, #shimmerStyle do
    local cuurentT = TEMPS.totalDuration;
    local duration = shimmerStyle[i].duration;
    local nextT = cuurentT + duration;
    TEMPS.totalDuration = nextT;
    local fromAlpha;
    if i > 1 then
        fromAlpha = shimmerStyle[i - 1].toAlpha;
        if fromAlpha < 0 then
            fromAlpha = 0;
        end
    else
        fromAlpha = 0;
    end
    local toAlpha = shimmerStyle[i].toAlpha;
    if toAlpha < 0 then
        toAlpha = 0;
    end
    local deltaAlpha = (toAlpha - fromAlpha)/duration
    timeAlpha[i] = {cuurentT, nextT, fromAlpha, deltaAlpha};
end
shimmerStyle = nil;

local function UpdateObjectAlphaByTime(object, t)
    local data;
    local tR, fromAlpha, deltaAlpha;
    for i = 1, TIER_SHIMMER do
        data = timeAlpha[i];
        if t >= data[1] and t <= data[2] then
            fromAlpha, deltaAlpha = data[3], data[4];
            tR = t - data[1];
            break;
        end
    end

    if tR then
        object:SetAlpha(fromAlpha + deltaAlpha*tR);
        return true
    else
        object:SetAlpha(0);
        return false
    end
end

NarciUIShimmerButtonMixin = {};

function NarciUIShimmerButtonMixin:Preload()
    local controller = AddControllerToWidget(self);
    controller:SetScript("OnUpdate", function(controller, elapsed)
        if controller.isHolding then
            local toAlpha = controller.toAlpha or 0;
            local alpha;
            if toAlpha == 0 then
                alpha = self.Shimmer:GetAlpha() - 1 * elapsed;
            else
                alpha = self.Shimmer:GetAlpha() + 5 * elapsed;
            end
            if alpha >= 0.8 then
                self.Shimmer:SetAlpha(0.8);
                controller:Hide();
            elseif alpha <= 0 then
                self.Shimmer:SetAlpha(0);
                if controller.pendingReset then
                    controller.pendingReset = nil;
                    controller.isHolding = nil;
                end
            else
                self.Shimmer:SetAlpha(alpha);
            end
        else
            local t = controller.t + elapsed;
            if not UpdateObjectAlphaByTime(self.Shimmer, t) then
                t = 0;
            end
            controller.t = t;
        end
    end);
    self.Shimmer:SetAlpha(0);
    self.Shimmer:Hide();
    self.Preload = nil;
end

function NarciUIShimmerButtonMixin:PlayShimmer()
    self.Shimmer:Show();
    if self.controller.isHolding then
        self.controller.toAlpha = 0;
        self.controller.pendingReset = true;
    end
    self.controller:Show();
end

function NarciUIShimmerButtonMixin:StopShimmer()
    self.Shimmer:Hide();
    self.controller:Hide();
    self.controller.toAlpha = 0;
    self.controller.isHolding = true;
end

function NarciUIShimmerButtonMixin:HoldShimmer()
    self.controller.toAlpha = 0.8;
    self.controller.isHolding = true;
    self.Shimmer:Show();
    self.controller:Show();
end


--------------------------------------------------------------------------------------------------
--Type: Color Picker (HSV)
local ColorPicker;
local RGBRatio2HSV = NarciAPI.RGBRatio2HSV;
local HSV2RGB = NarciAPI.HSV2RGB;


NarciColorPickerSliderMixin = {};

function NarciColorPickerSliderMixin:SetBorderOffset(x)
    self.Left:SetPoint("LEFT", self, "LEFT", x, 0);
    self.Right:SetPoint("RIGHT", self, "RIGHT", -x, 0);
end

function NarciColorPickerSliderMixin:SetButtonSize(w, h)
    self.Left:SetSize(h, h);
    self.Right:SetSize(h, h);
    self.Center:SetHeight(h);
    self:SetSize(w, h);
end

function NarciColorPickerSliderMixin:SetHighlight(state)
    local v;
    if state then
        v = 1;
    else
        v = 0.66;
    end
    for i = 1, #self.borderTextures do
        self.borderTextures[i]:SetVertexColor(v, v, v);
    end
end

function NarciColorPickerSliderMixin:OnEnter()
    if not IsMouseButtonDown() then
        self:SetHighlight(true);
    end
end

function NarciColorPickerSliderMixin:OnLeave()
    if not IsMouseButtonDown() then
        self:SetHighlight(false);
    end
end

function NarciColorPickerSliderMixin:OnMouseUp()
    if not self:IsMouseOver() then
        self:SetHighlight(false);
    end
end

function NarciColorPickerSliderMixin:OnLoad()
    local tex = "Interface\\AddOns\\Narcissus\\Art\\Widgets\\ColorPicker\\UI.tga";
    self.borderTextures = {
        self.Left, self.Center, self.Right, self.ThumbTexture
    };

    for i = 1, #self.borderTextures do
        self.borderTextures[i]:SetTexture(tex);
    end
    self.Marker:SetTexture(tex);
    self.Left:SetTexCoord(0, 0.125, 0, 0.125);
    self.Center:SetTexCoord(0.125, 0.875, 0, 0.125);
    self.Right:SetTexCoord(0.875, 1, 0, 0.125);
    self.ThumbTexture:SetTexCoord(0, 0.0625, 0.875, 1);
    self.Marker:SetTexCoord(0.125, 0.140625, 0.875, 1);
    self:SetHighlight(false);
    self:SetBorderOffset(2);
    self.Marker:SetVertexColor(0.5, 0.5, 0.5);
end


function NarciColorPickerSliderMixin:OnValueChanged(value, isUserInput)
    if value == self.value then
        return
    end
    self.ThumbTexture:SetPoint("CENTER", self.Thumb, "CENTER", 0, 0);
    --self.Marker:SetPoint("CENTER", self.Thumb, "CENTER", 0, 0);

    self.value = value;

    ColorPicker:Update();
end


--Confirm/Cancel New Color

NarciColorPickerActionButtonMixin = {};

function NarciColorPickerActionButtonMixin:OnLoad()
    local tex = "Interface\\AddOns\\Narcissus\\Art\\Widgets\\ColorPicker\\UI.tga";
    self.borderTextures = {
        self.LeftEnd, self.Left, self.Center, self.Right, self.RightEnd
    }
    for i = 1, #self.borderTextures do
        self.borderTextures[i]:SetTexture(tex);
    end
    self.LeftEnd:SetTexCoord(0, 0.125, 0.3750, 0.6250);
    self.Left:SetTexCoord(0.125, 0.25, 0.3750, 0.6250);
    if self.action == "Confirm" then
        self.Center:SetTexCoord(0.505, 0.75, 0.3750, 0.6250);
    else
        self.Center:SetTexCoord(0.75, 1, 0.3750, 0.6250);
    end
    self.Right:SetTexCoord(0.125, 0.25, 0.3750, 0.6250);
    self.RightEnd:SetTexCoord(0.375, 0.5, 0.3750, 0.6250);

    self:SetHighlight(false);
end

function NarciColorPickerActionButtonMixin:SetButtonHeight(height)
    self:SetHeight(height);
    self.Reference:SetHeight(height - 3);
end

function NarciColorPickerActionButtonMixin:SetButtonWidth(width)
    self:SetWidth(width);
    self.Reference:SetWidth(width - 1);
end

function NarciColorPickerActionButtonMixin:SetButtonSize(w, h)
    self.LeftEnd:SetSize(h, 2*h);
    self.RightEnd:SetSize(h, 2*h);
    self.Center:SetSize(2*h, 2*h);
    self.Left:SetHeight(2*h);
    self.Right:SetHeight(2*h);
    self:SetButtonWidth(w);
    self:SetButtonHeight(h);
end

function NarciColorPickerActionButtonMixin:SetHighlight(state)
    local v;
    if state then
        v = 1;
    else
        v = 0.66;
    end
    for i = 1, #self.borderTextures do
        self.borderTextures[i]:SetVertexColor(v, v, v);
    end
end

function NarciColorPickerActionButtonMixin:OnEnter()
    self:SetHighlight(true);
end

function NarciColorPickerActionButtonMixin:OnLeave()
    self:SetHighlight(false);
end

function NarciColorPickerActionButtonMixin:OnMouseUp()
    self.Reference:SetPoint("CENTER", self, "CENTER", 0, 0);
end

function NarciColorPickerActionButtonMixin:OnMouseDown()
    self.Reference:SetPoint("CENTER", self, "CENTER", 0, -1);
end

function NarciColorPickerActionButtonMixin:OnClick()
    ColorPicker[self.action](ColorPicker);
end

function NarciColorPickerActionButtonMixin:SetColor(r, g, b)
    self.Reference:SetColorTexture(r, g, b);
    self.color = {r, g, b};
end

function NarciColorPickerActionButtonMixin:GetColor()
    if self.color then
        return unpack(self.color)
    else
        return 0, 0, 0
    end
end

NarciUIColorPickerMixin = {};

function NarciUIColorPickerMixin:Preload()
    ColorPicker = self;
    self:RegisterForDrag("LeftButton");

    local sliderHeight = 12;
    local sliderWidth = 160;
    local thumbWidth = 6;

    local HueSlider = self.HueSlider;
    local colors = {
        {1, 0, 0},      --Red
        {1, 1, 0},      --Yellow
        {0, 1, 0},      --Green
        {0, 1, 1},      --Cyan
        {0, 0, 1},      --Blue
        {1, 0, 1},      --Pink
        {1, 0, 0},      --Red
    }
    local gt;
    local gradients = {};
    local numBlocks = #colors - 1;
    local blockWidth = (sliderWidth - thumbWidth) / numBlocks;
    for i = 1, numBlocks do
        gt = HueSlider:CreateTexture(nil, "ARTWORK");
        gradients[i] = gt;
        gt:SetSize(blockWidth, sliderHeight);
        if i == 1 then
            gt:SetPoint("LEFT", HueSlider, "LEFT", thumbWidth/2 + blockWidth * (i - 1), 0);
        else
            gt:SetPoint("LEFT", gradients[i - 1], "RIGHT", 0, 0);
        end
        gt:SetColorTexture(1, 1, 1, 1);
        SetGradient(gt, "HORIZONTAL", colors[i][1], colors[i][2], colors[i][3],  colors[i+1][1], colors[i+1][2], colors[i+1][3]);
    end
    HueSlider:SetMinMaxValues(0, 360);
    HueSlider:SetValueStep(1);
    HueSlider:SetWidth(sliderWidth);
    HueSlider:SetValue(0);
    HueSlider.value = 0;

    local SatSlider = self.SaturationSlider;
    local gt2 = SatSlider:CreateTexture(nil, "ARTWORK");
    SatSlider.Gradient = gt2;
    gt2:SetSize(sliderWidth - thumbWidth, sliderHeight);
    gt2:SetPoint("LEFT", SatSlider, "LEFT", thumbWidth/2, 0);
    gt2:SetColorTexture(1, 1, 1, 1);
    SetGradient(gt2, "HORIZONTAL", 1, 1, 1, 1, 0, 0);
    SatSlider:SetMinMaxValues(0, 100);
    SatSlider:SetValueStep(1);
    SatSlider:SetWidth(sliderWidth);
    SatSlider:SetValue(100);
    SatSlider.value = 100;
    
    local BriSlider = self.BrightnessSlider;
    local gt3 = SatSlider:CreateTexture(nil, "ARTWORK");
    BriSlider.Gradient = gt3;
    gt3:SetSize(sliderWidth - thumbWidth, sliderHeight);
    gt3:SetPoint("LEFT", BriSlider, "LEFT", thumbWidth/2, 0);
    gt3:SetColorTexture(1, 1, 1, 1);
    SetGradient(gt3, "HORIZONTAL", 0, 0, 0, 1, 0, 0);
    BriSlider:SetMinMaxValues(0, 100);
    BriSlider:SetValueStep(1);
    BriSlider:SetWidth(sliderWidth);
    BriSlider:SetValue(100);

    local ConfirmButton = self.ConfirmButton;
    local CancelButton = self.CancelButton;
    
    local padding = 4;
    local sliders = {HueSlider, SatSlider, BriSlider};
    for i = 1, #sliders do
        sliders[i]:SetButtonSize(sliderWidth, sliderHeight);
        sliders[i]:SetParent(self.FrameContainer);
    end

    HueSlider:SetPoint("TOP", self, "TOP", 0, -padding);
    SatSlider:SetPoint("TOP", self, "TOP", 0, -2*padding - sliderHeight);
    BriSlider:SetPoint("TOP", self, "TOP", 0, -3*padding - 2*sliderHeight);
    ConfirmButton:SetPoint("TOPRIGHT", self, "TOP", -padding, -5*padding - 3*sliderHeight);
    CancelButton:SetPoint("TOPLEFT", self, "TOP", padding, -5*padding - 3*sliderHeight);
    ConfirmButton:SetButtonSize(sliderWidth/2 - padding - thumbWidth/4, sliderHeight);
    CancelButton:SetButtonSize(sliderWidth/2 - padding - thumbWidth/4, sliderHeight);
    CancelButton:SetColor(0, 0, 0);

    self:SetSize(sliderWidth + 2*padding - 4, 4*sliderHeight + 6*padding + 1);
    self.Preload = nil;

    self:SetRGB(0, 0, 0);
    self:Update();
end

function NarciUIColorPickerMixin:OnLoad()
    self:Preload();
    self:Hide();
end

function NarciUIColorPickerMixin:Update()
    if self.Preload then return end;

    local h, s, v = self:GetHSV();
    local r, g, b = HSV2RGB(h, 1, v);
    SetGradient(self.SaturationSlider.Gradient, "HORIZONTAL", v, v, v, r, g, b);
    r, g, b = HSV2RGB(h, s, 1);
    SetGradient(self.BrightnessSlider.Gradient, "HORIZONTAL", 0, 0, 0, r, g, b);
    r, g, b = self:GetRGB();
    self.ConfirmButton:SetColor(r, g, b);

    if self.objects then
        for i = 1, #self.objects do
            self.objects[i]:SetVertexColor(r, g, b);
        end
    end
end

function NarciUIColorPickerMixin:SetRGB(r, g, b)
    local h, s, v = RGBRatio2HSV(r, g, b);
    self.HueSlider:SetValue(h);
    self.SaturationSlider:SetValue(100*s);
    self.BrightnessSlider:SetValue(100*v);
    self.ConfirmButton:SetColor(r, g, b);
end

function NarciUIColorPickerMixin:GetHSV()
    return self.HueSlider.value or 0, (self.SaturationSlider.value or 100)/100, (self.BrightnessSlider.value or 100)/100;
end

function NarciUIColorPickerMixin:GetRGB()
    return HSV2RGB(self:GetHSV());
end

function NarciUIColorPickerMixin:OnDragStart()
    self:StartMoving();
end

function NarciUIColorPickerMixin:OnDragStop()
    self:StopMovingOrSizing();
end

function NarciUIColorPickerMixin:FadeButton(buttonIndex)
    self.ConfirmButton:Disable();
    self.CancelButton:Disable();
    FadeFrame(self.FrameContainer, 0.2, 0);
    if buttonIndex == 1 then
        FadeFrame(self.CancelButton, 0.2, 0);
    else
        FadeFrame(self.ConfirmButton, 0.2, 0);
    end
    After(0.5, function()
        FadeFrame(self, 0.25, 0);
    end);

    --After(3, function()
    --    self:ShowPanel();
    --end)
end

function NarciUIColorPickerMixin:Confirm()
    self:FadeButton(1);
    self:Update();
end

function NarciUIColorPickerMixin:Cancel()
    self:FadeButton(2);

    local r, g, b = self.CancelButton:GetColor();
    ColorPicker:SetRGB(r, g, b);
end

function NarciUIColorPickerMixin:ShowPanel()
    self.FrameContainer:Show();
    self.FrameContainer:SetAlpha(1);
    self.ConfirmButton:Show();
    self.ConfirmButton:SetAlpha(1);
    self.CancelButton:Show();
    self.CancelButton:SetAlpha(1);
    self.ConfirmButton:Enable();
    self.CancelButton:Enable();
    self:Show();
    self:SetAlpha(1);
end

function NarciUIColorPickerMixin:SetObject(switch)
    self.objects = switch.objects;
    self:ClearAllPoints();
    self:SetPoint("TOPLEFT", switch, "TOPRIGHT", 8, 0);

    if self.objects and self.objects[1] then
        --self:SetRGB(self.objects[1]:GetVertexColor());
        self.CancelButton:SetColor(self.objects[1]:GetVertexColor());
    end

    self:ShowPanel();
end

--------------------------------------------------------------------------------------------------
--Name: Skewed Rectangular Button
--Notes: Clockwise 10 Degree

local MARGIN_X = 5;
NarciShewedRectButtonMixin = {};

function NarciShewedRectButtonMixin:SetButtonSize(width, height)
    self:SetSize(width, height);
    self.MaskLeft:SetSize(MARGIN_X, height);
    self.MaskRight:SetSize(MARGIN_X, height);

    local texOffset = 0.05;
    local dy = (1 - 2*texOffset)*height/width/2;
    self.Icon:SetTexCoord(texOffset, 1 - texOffset, 0.5 - dy, 0.5 + dy);
    self.Icon:SetSize(width, height);
end

function NarciShewedRectButtonMixin:SetIcon(iconFile)
    self.Icon:SetTexture(iconFile);
end

function NarciShewedRectButtonMixin:SetColorTexture(r, g, b)
    self.Icon:SetColorTexture(r, g, b);
end

function NarciShewedRectButtonMixin:ShowAlert()
    self:SetIcon("Interface\\AddOns\\Narcissus\\Art\\NavBar\\AlertMark");
    self.Icon:Show();
end

function NarciShewedRectButtonMixin:SetHighlight(state)
    if state then
        self.Icon:SetDesaturation(0);
        self.Icon:SetVertexColor(1, 1, 1);
    else
        self.Icon:SetDesaturation(0.2);
        self.Icon:SetVertexColor(0.80, 0.80, 0.80);
    end
end

function NarciShewedRectButtonMixin:UseFullMask(state, side)
    local mode = "CLAMPTOWHITE";    --CLAMPTOBLACKADDITIVE

    if state and (not side or side == 1) then
        self.MaskLeft:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Masks\\Full", mode, mode);
    else
        self.MaskLeft:SetTexture("Interface\\AddOns\\Narcissus\\Art\\SkewdRect\\ReversedMask-Left", mode, mode);
    end

    if state and (not side or side == 2) then
        self.MaskRight:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Masks\\Full", mode, mode);
    else
        self.MaskRight:SetTexture("Interface\\AddOns\\Narcissus\\Art\\SkewdRect\\ReversedMask-Right", mode, mode);
    end
end


--------------------------------------------------------------------------------------------------
--Name: Progress Timer
--Notes: Timer & Progress Bar
NarciProgressTimerMixin = {};

function NarciProgressTimerMixin:SetOnFinishedFunc(func1, func2)
    self.Fluid.animFill.onFinishedFunc = func1;
    self.Fluid.animFade.onFinishedFunc = func2;
end

function NarciProgressTimerMixin:SetTimer(duration, loopTimer)
    self:StopAnimating();
    local animFill = self.Fluid.animFill;
    animFill.s1:SetDuration(duration);
    if loopTimer then
        self.isLoop = true;
    else
        self.isLoop = false;
    end
    animFill:Play();
end

function NarciProgressTimerMixin:Start()
    self.Fluid.animFill:Play();
    self:Show();
end

function NarciProgressTimerMixin:Stop()
    self:StopAnimating();
    self:Hide();
end

function NarciProgressTimerMixin:Pause()
    if not self.isPaused then
        self.isPaused = true;
        self.Fluid.animFill:Pause();
    end
end

function NarciProgressTimerMixin:Play()
    if self.isPaused then
        self.isPaused = nil;
        if not self.Fluid.animFade:IsPlaying() then
            self.Fluid.animFill:Play();
        end
    end
end

function NarciProgressTimerMixin:Resume()
    if self:IsShown() then
        self:Play();
    end
end

function NarciProgressTimerMixin:SetAlign(widget, offsetY)
    self:ClearAllPoints();
    offsetY = offsetY or 0;
    self:SetPoint("BOTTOMLEFT", widget, "BOTTOMLEFT", 1, offsetY);
    self:SetPoint("BOTTOMRIGHT", widget, "BOTTOMRIGHT", -1, offsetY);
end

function NarciProgressTimerMixin:SetColor(r, g, b)
    self.Fluid:SetColorTexture(r, g, b);
end


--------------------------------------------------------------------------------------------------
--Name: Custom Slider
--Notes:
--1. A frame with all slider's methods;
--2. Take into account the MouseDown Position
local offsetUpdater = CreateFrame("Frame");
offsetUpdater.t = 0;
offsetUpdater:Hide();

offsetUpdater:SetScript("OnUpdate", function(self)
    self.x, self.y = GetCursorPosition();
    self.ratio = (self.yTop - self.y + self.yOffset)/self.pixelRange;
    self.scrollBar:SetValueByRatio(self.ratio);
end)

function offsetUpdater:Start(scrollBar)
    local uiScale = scrollBar:GetEffectiveScale();
    self.scrollBar = scrollBar;
    self.yTop = scrollBar:GetTop() * uiScale;
    self.pixelRange = scrollBar.thumbRange * uiScale;
    local yoffset;
    if scrollBar.ThumbTexture:IsMouseOver(0, 0, -2, 0) then
        local _, thumbY, mouseY;
        _, mouseY = GetCursorPosition();
        thumbY = scrollBar.ThumbTexture:GetTop() * uiScale;
        self.yOffset = -thumbY + mouseY;
    else
        self.yOffset = -0.5 * scrollBar.ThumbTexture:GetHeight() * uiScale;
    end
    self:Show();
end

function offsetUpdater:Stop()
    self:Hide();
end

NarciCustomScrollBarMixin = {};

function NarciCustomScrollBarMixin:OnLoad()
    self.thumbRange = 0;
    self.value = 0;

    self:SetMinMaxValues(0, 0);
    self:SetThumbAlpha(0.25);

    local parentScrollFrame = self:GetParent();
    self.onValueChangedFunc = function(value)
        parentScrollFrame:SetVerticalScroll(value);
    end
end

function NarciCustomScrollBarMixin:SetMinMaxValues(minValue, maxValue)
    if minValue > maxValue then
        minValue = 0;
        maxValue = 0;
    end

    self.minValue = minValue;
    self.maxValue = maxValue;

    self:SetShown(maxValue ~= 0)
end

function NarciCustomScrollBarMixin:SetValue(value)
    if value < self.minValue then
        value = self.minValue;
    elseif value > self.maxValue then
        value = self.maxValue;
    end
    self.value = value;

    if self.maxValue ~= 0 then
        self.ThumbTexture:SetPoint("TOPRIGHT", self, "TOPRIGHT", 0, -value / self.maxValue * self.thumbRange);
    end

    self.onValueChangedFunc(value);
end

function NarciCustomScrollBarMixin:SetValueByRatio(ratio)
    if ratio < 0 then
        ratio = 0;
    elseif ratio > 1 then
        ratio = 1;
    end
    self:SetValue( ratio * (self.maxValue - self.minValue) )
end

function NarciCustomScrollBarMixin:GetValue()
    return self.value or 0;
end

function NarciCustomScrollBarMixin:OnEnter()
    if IsMouseButtonDown() then
        return
    end
    if self.ThumbTexture:IsMouseOver() then
        self:SetThumbAlpha(1);
    else
        self:SetThumbAlpha(0.66);
    end
end

function NarciCustomScrollBarMixin:OnLeave()
    if not self.isDragging then
        self:SetThumbAlpha();
    end
end

function NarciCustomScrollBarMixin:OnMouseDown()
    self.isDragging = true;
    offsetUpdater:Start(self);
    self:SetThumbAlpha(1);
    if self.onMouseDownFunc then
        self.onMouseDownFunc();
    end
end

function NarciCustomScrollBarMixin:OnMouseUp()
    self.isDragging = nil;
    offsetUpdater:Stop();
    if self:IsMouseOver() then
        self:SetThumbAlpha(0.66);
    else
        self:SetThumbAlpha();
    end
end

function NarciCustomScrollBarMixin:OnHide()
    self:SetThumbAlpha();
end

function NarciCustomScrollBarMixin:SetThumbAlpha(alpha)
    alpha = alpha or 0.4;
    self.ThumbTexture:SetAlpha(alpha);
end

function NarciCustomScrollBarMixin:SetRange(fullRange, changeThumbHeight)
    local ScrollFrame = self:GetParent();
    local thumbHeight = 16;
    local barHeight = self:GetHeight() or 0;

    if not fullRange or fullRange <= 0.5 then
        fullRange = 0;
        self:Hide();
    else
        self:SetMinMaxValues(0, fullRange);
        if changeThumbHeight then
            thumbHeight = math.floor(barHeight^2 / fullRange);
            if thumbHeight > barHeight - 24 then
                thumbHeight = barHeight - 24;
            end
            if thumbHeight < 16 then
                thumbHeight = 16;
            end
        end
        self.ThumbTexture:SetHeight(thumbHeight);
    end

    self.thumbRange = barHeight - thumbHeight;
    ScrollFrame.range = fullRange;
end

function NarciCustomScrollBarMixin:OnMinMaxChanged(min, max)

end


--------------------------------------------------------------------------------------------------
NarciInteractableModelMixin = {};

local function UpdateCameraPosition(model)
    model:SetCameraPosition(model.cameraDistance * math.sin(model.cameraPitch), 0, model.cameraDistance * math.cos(model.cameraPitch) + 0.8);
end

local function UpdateCameraPitch(model, pitch)
	model.cameraPitch = pitch;
	UpdateCameraPosition(model);
end

function NarciInteractableModelMixin:OnLoad()
	self.rotation = 0.61;
	self:SetFacing(self.rotation);
    self:MakeCurrentCameraCustom();
    self:SetCameraTarget(0, 0, 0);
    self.cameraPitch = self:GetPitch();
    self.cameraDistance = self:GetCameraDistance()
end

function NarciInteractableModelMixin:StartPanning()
	self.isAltDown = IsAltKeyDown();
	self.panning = true;
	local posX, posY, posZ = self:GetPosition();
	self.posX = posX;
	self.posY = posY;
	self.posZ = posZ;
	local cursorX, cursorY = GetCursorPosition();
	self.cursorX = cursorX;
	self.cursorY = cursorY;
	self.zoomCursorStartX, self.zoomCursorStartY = cursorX, cursorY;
end

function NarciInteractableModelMixin:OnMouseWheel(delta)
	if not self:HasCustomCamera() then return; end
    self.cameraDistance = self.cameraDistance - delta * 0.25
	UpdateCameraPosition(self);
end

function NarciInteractableModelMixin:OnMouseUp(button)
	if ( button == "RightButton" and self.panning ) then
		self.panning = false;
	elseif ( self.mouseDown ) then
		if ( not button or button == "LeftButton" ) then
			self.mouseDown = false;
		end
	end
end

function NarciInteractableModelMixin:OnMouseDown(button)
	if ( button == "RightButton" and not self.mouseDown ) then
		self:StartPanning();
	else
		if ( not button or button == "LeftButton" ) then
			self.mouseDown = true;
			self.rotationCursorStart, self.cameraPitchCursorStart = GetCursorPosition();
		end
	end
end

function NarciInteractableModelMixin:OnModelLoaded()
    self:MakeCurrentCameraCustom();
    self:SetCameraTarget(0, 0, 0);
    self.cameraDistance = self:GetCameraDistance();
end

function NarciInteractableModelMixin:OnHide()
	if ( self.panning ) then
		self.panning = false;
	end
	self.mouseDown = false;
end

function NarciInteractableModelMixin:OnUpdate()
	-- Mouse drag rotation
	if (self.mouseDown) then
		if ( self.rotationCursorStart ) then
			local x, y = GetCursorPosition();
			local diffX = (x - self.rotationCursorStart) * 0.01;	--MODELFRAME_DRAG_ROTATION_CONSTANT
			local diffY = (y - self.cameraPitchCursorStart) * 0.02;
			self.rotationCursorStart, self.cameraPitchCursorStart = GetCursorPosition();

			if not IsAltKeyDown() then
				--Rotate Character
				self.rotation = self.rotation + diffX;
				if ( self.rotation < 0 ) then
					self.rotation = self.rotation + (2 * pi);
				end
				if ( self.rotation > (2 * pi) ) then
					self.rotation = self.rotation - (2 * pi);
				end
				self:SetFacing(self.rotation, false);
			else
				--Rotate Camera (pitch)
				self.cameraPitch = self.cameraPitch + diffY;
				if ( self.cameraPitch <= (0 + 0.01)) then
					self.cameraPitch = 0.01;
				end
				if ( self.cameraPitch >= ( pi - 0.01)) then
					self.cameraPitch = pi - 0.01;
				end
				UpdateCameraPitch(self, self.cameraPitch);
			end
		end
	elseif ( self.panning ) then
		local isAltDown = IsAltKeyDown();
		if isAltDown ~= self.isAltDown then
			--Reset cursor positions
			self:StartPanning();
		end
		local modelScale = self:GetModelScale();
		local cursorX, cursorY = GetCursorPosition();
		local scale = UIParent:GetEffectiveScale();
		local diff = (cursorX - self.zoomCursorStartX) + (cursorY - self.zoomCursorStartY);
		self.zoomCursorStartX, self.zoomCursorStartY = GetCursorPosition();
		if not isAltDown then
			local zoom = sqrt(sqrt(self.cameraDistance));
			local transformationRatio = 0.00002* 40 * 2 ^ (zoom * 2) * scale / modelScale;
			local dx = (cursorX - self.cursorX) * transformationRatio;
			local dy = (cursorY - self.cursorY) * transformationRatio;
			local posY = self.posY + dx;
			local posZ = self.posZ + dy;
			self:SetPosition(self.posX, posY, posZ);
			--print("Y: "..posY.." Z: "..posZ.." Dis: "..self.cameraDistance)
		else
			self.cameraDistance = self.cameraDistance - diff * 0.01;
			UpdateCameraPosition(self);
		end
	end
end

--------------------------------------------------------------------------------------------------
--Name: Search Box
local function OnDeletePressed(widget, key)
    if key == "DELETE" then
        widget:ClearText(true);
        return true
    end
end

NarciSearchBoxSharedMixin = {};

function NarciSearchBoxSharedMixin:OnLoad()
    local delayedSearch = NarciAPI_CreateAnimationFrame(0.5);
    self.delayedSearch = delayedSearch;
    delayedSearch:SetScript("OnUpdate", function(frame, elapsed)
        frame.total = frame.total + elapsed;
        if frame.total >= frame.duration then
            frame:Hide();
            if self.onSearchFunc then
                self.onSearchFunc( self:GetText() )
            end
        end
    end)
    self:OnLeave();
    self.onDeletePressedFunc = OnDeletePressed;
    self.onKeyDownFunc = OnDeletePressed;
end

function NarciSearchBoxSharedMixin:OnShow()
    self:SetFocus();
end

function NarciSearchBoxSharedMixin:OnHide()
    self.delayedSearch:Hide();
end

function NarciSearchBoxSharedMixin:OnTextChanged(isUserInput)
    local str = self:GetText();
    if str and str ~= "" then
        self.DefaultText:Hide();
        self.EraseButton:Show();
    else
        self.DefaultText:Show();
        self.EraseButton:Hide();
    end

    if isUserInput then
        self:Search(true);
    end
    self.NoMatchText:Hide();
end

function NarciSearchBoxSharedMixin:QuitEdit()
    self:ClearFocus();
end

function NarciSearchBoxSharedMixin:OnTabPressed()
    self:HighlightText();
end

function NarciSearchBoxSharedMixin:OnEditFocusGained()
    self:HighlightText();
    self:SetScript("OnKeyDown", self.onKeyDownFunc);
end

function NarciSearchBoxSharedMixin:OnEditFocusLost()
    self:HighlightText(0, 0);
    self:OnLeave();
    self:SetScript("OnKeyDown", nil);

    if self:IsMouseOver() and IsMouseButtonDown("LeftButton") then
        self.EraseButton.isEditing = true;
    else
        self.EraseButton.isEditing = false;
    end
end

function NarciSearchBoxSharedMixin:Search(on)
    self.delayedSearch:Hide();
    if on then
        self.delayedSearch:Show();
    end
end

function NarciSearchBoxSharedMixin:ClearText(reset)
    self:SetText("");
    self.DefaultText:Show();
    self.EraseButton:Hide();
end

function NarciSearchBoxSharedMixin:OnEnter()
    self.DefaultText:SetTextColor(0.52, 0.52, 0.52);
end

function NarciSearchBoxSharedMixin:OnLeave()
    if not self:IsMouseOver() then
        self.DefaultText:SetTextColor(0.42, 0.42, 0.42);
    end
end


--------------------------------------------------------------------------------------------------
--Show a L/R mark next to the item border

NarciTransmogSlotDirectionMarkMixin = {};

function NarciTransmogSlotDirectionMarkMixin:SetDirection(direction)
    if direction == 1 then
        self.tooltip = LEFTSHOULDERSLOT or "Left Shoulder";
        self.Letter:SetTexCoord(0, 0.5, 0, 0.5);
        self.Triangle:SetTexCoord(0, 0.5, 0.5, 1);
    else
        self.tooltip = RIGHTSHOULDERSLOT or "Right Shoulder";
        self.Letter:SetTexCoord(0.5, 1, 0, 0.5);
        self.Triangle:SetTexCoord(0.5, 1, 0.5, 1);
    end
end

function NarciTransmogSlotDirectionMarkMixin:SetColor(r, g, b)
    self.Letter:SetVertexColor(r, g, b);
end

function NarciTransmogSlotDirectionMarkMixin:SetQualityColor(itemQuality)
    self:SetColor(NarciAPI.GetItemQualityColor(itemQuality));
end

function NarciTransmogSlotDirectionMarkMixin:OnEnter()

end

function NarciTransmogSlotDirectionMarkMixin:OnLeave()

end

function NarciTransmogSlotDirectionMarkMixin:OnHide()

end


--------------------------------------------------------------------------------------------------
local CreateKeyChordStringUsingMetaKeyState = CreateKeyChordStringUsingMetaKeyState;
--Name: Clipboard
--Notes: Highlight the border when gaining focus. Show visual feedback (glow) after pressing Ctrl+C

local HotkeyListener = CreateFrame("Frame");
HotkeyListener:SetFrameStrata("TOOLTIP");
HotkeyListener:Hide();
HotkeyListener:SetPropagateKeyboardInput(true);
HotkeyListener:SetScript("OnKeyDown", function(self, key)
    local keys = CreateKeyChordStringUsingMetaKeyState(key);
    if keys == "CTRL-C" or key == "COMMAND-C" then
        if self.parentEditBox then
            self:Hide();
            After(0, function()
                --Texts won't be copied if the editbox hides immediately
                self.parentEditBox:OnSuccess();
            end);
        end
    end
end);

function HotkeyListener:SetParentObject(editbox)
    self.parentEditBox = editbox;
    self:Show();
end

function HotkeyListener:Stop(editbox)
    if self.parentEditBox == editbox then
        self:Hide();
    end
end

NarciResponsiveEditBoxSharedMixin = {};

function NarciResponsiveEditBoxSharedMixin:OnEditFocusGained()
    self:HighlightText();
    if self.LockHighlight then
        self:LockHighlight(true);
    else
        self:GetParent():GetParent():LockHighlight(true);
    end

    HotkeyListener:SetParentObject(self);
end

function NarciResponsiveEditBoxSharedMixin:OnEditFocusLost()
    self:HighlightText(0, 0);
    if self.LockHighlight then
        self:LockHighlight(false);
    else
        self:GetParent():GetParent():LockHighlight(false);
    end
    HotkeyListener:Stop(self);
end

function NarciResponsiveEditBoxSharedMixin:OnSuccess()
    if self.Glow then
        self:Glow(false);
    else
        self:GetParent():GetParent():Glow();
    end
    self:QuitEdit();
    if self.onCopiedCallback then
        self.onCopiedCallback(self);
    end
end

function NarciResponsiveEditBoxSharedMixin:QuitEdit()
    self:ClearFocus();
end

function NarciResponsiveEditBoxSharedMixin:OnHide()
    self:QuitEdit();
    self.numInput = nil;
    self:StopAnimating();
end

function NarciResponsiveEditBoxSharedMixin:OnTextChanged(userInput)

end

function NarciResponsiveEditBoxSharedMixin:SetDefaultCursorPosition(offset)
    self:SetCursorPosition(offset);
    self.defaultCursorPosition = offset;
end

--Subtype: The editbox itself has a border
NarciResponsiveClipboardMixin = CreateFromMixins(NarciFrameBorderMixin, NarciResponsiveEditBoxSharedMixin);

function NarciResponsiveClipboardMixin:OnTextChanged(userInput)
    if userInput then
        self:SetText(self.copiedText);
        if self.defaultCursorPosition then
            self:SetCursorPosition(self.defaultCursorPosition);
        end
        if not self.numInput then
            self.numInput = 0;
        end
        self.numInput = self.numInput + 1;
        if self.numInput > 3 then
            self.numInput = nil;
            self:QuitEdit();
        end
    else
        self.copiedText = self:GetText();
    end
end

function NarciResponsiveEditBoxSharedMixin:OnHide()
    self:QuitEdit();
    self.copiedText = nil;
end

--Subtype: Frame-ScrollFrame-EditBox
NarciScrollEditBoxMixin = CreateFromMixins(NarciFrameBorderMixin);

function NarciScrollEditBoxMixin:SetText(str)
    self.ScrollFrame.EditBox:SetText(str);
    After(0, function()
        self:UpdateScrollRange(true);
    end);
end

function NarciScrollEditBoxMixin:SetFocus()
    self.ScrollFrame.EditBox:SetFocus();
end

function NarciScrollEditBoxMixin:ClearFocus()
    self.ScrollFrame.EditBox:ClearFocus();
end

function NarciScrollEditBoxMixin:UpdateScrollRange(resetOffset)
    if resetOffset then
        self.ScrollFrame.scrollBar:SetValue(0);
    end

    local editBoxHeight = self.ScrollFrame.EditBox:GetHeight();
    local scrollFrameHeight = self.ScrollFrame:GetHeight();
    local range = math.floor(editBoxHeight - scrollFrameHeight + 0.5);
    self.ScrollFrame.scrollBar:SetRange(range, true);
end

function NarciScrollEditBoxMixin:OnMouseDown()
    self.ScrollFrame.EditBox:SetFocus();
end

function NarciScrollEditBoxMixin:PostLoad()
    self:OnLoad();
    self.ScrollFrame.scrollBar:OnLoad();

    self.ScrollFrame.buttonHeight = 14;
    self.ScrollFrame.scrollBar:SetRange(0, true);
    NarciAPI_SmoothScroll_Initialization(self.ScrollFrame, nil, nil, 2, 0.14);
end


--------------------------------------------------------------------------------------------------
--Notes: Hide the frame if user clicks anywhere other than the frame itself or the switch used to open that frame.

NarciAutoCloseFrameMixin = {};

function NarciAutoCloseFrameMixin:OnShow()
    if not self.setParentObjectManually then
        self.parentObject = self:GetParent();
    end
    self:RegisterEvent("GLOBAL_MOUSE_DOWN");

    if self.autoClose then
        self.AutoCloseTimer:Play();
    end
end

function NarciAutoCloseFrameMixin:OnHide()
    self.parentObject = nil;
    self:UnregisterEvent("GLOBAL_MOUSE_DOWN");
    self.AutoCloseTimer:Stop();
    self:Hide();
end

function NarciAutoCloseFrameMixin:OnEvent(event)
    if self:IsMouseOver() then
        self:RestartTimer();
    else
        if (not self.parentObject) or (self.parentObject and not self.parentObject:IsMouseOver()) then
            self:Hide();
        else
            self:RestartTimer();
        end
    end
end

function NarciAutoCloseFrameMixin:RestartTimer()
    self.AutoCloseTimer:Stop();
    if self.autoClose then
        self.AutoCloseTimer:Play();
    end
end

function NarciAutoCloseFrameMixin:OnTimerFinished()
    self:OnEvent("GLOBAL_MOUSE_DOWN");
end

function NarciAutoCloseFrameMixin:Toggle()
    self:SetShown(not self:IsShown());
end


--------------------------------------------------------------------------------------------------
--Simple Line Slider

NarciVerticalLineSliderMixin = {};

function NarciVerticalLineSliderMixin:OnLoad()
    self.Thumb:SetVertexColor(0.5, 0.5, 0.5);
    self.Background:SetVertexColor(0.25, 0.25, 0.25);
    self.Background:SetAlpha(0.4);
end

function NarciVerticalLineSliderMixin:OnEnter()
    self:FadeIn();
end

function NarciVerticalLineSliderMixin:OnLeave()
    if not self:IsDraggingThumb() then
        self:FadeOut();
    end
end

function NarciVerticalLineSliderMixin:OnMouseUp()
    if not self:IsMouseOver() then
        self:FadeOut();
    end
end

function NarciVerticalLineSliderMixin:FadeIn()
    FadeFrame(self.Background, 0.25, 1);
    self.Thumb:SetVertexColor(0.8, 0.8, 0.8);
end

function NarciVerticalLineSliderMixin:FadeOut()
    FadeFrame(self.Background, 0.25, 0.4);
    self.Thumb:SetVertexColor(0.5, 0.5, 0.5);
end


--------------------------------------------------------------------------------------------------
local _, screenHeight = GetPhysicalScreenSize();
local PIXEL = (768/screenHeight);
screenHeight = nil;

NarciLineBorderMixin = {};

function NarciLineBorderMixin:SetWeight(weight)
    self.weight = weight;
end

function NarciLineBorderMixin:SetColor(r, g, b)
    self.Stroke:SetColorTexture(r, g, b);   --default color is 0.25
end

function NarciLineBorderMixin:OnLoad()
    self.Exclusion:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Masks\\Exclusion", "CLAMPTOWHITE", "CLAMPTOWHITE", "NEAREST");
    self:SetWeight(2);
    self:UpdateStroke();
end

function NarciLineBorderMixin:OnShow()
    self:UpdateStroke();
end

function NarciLineBorderMixin:UpdateStroke()
    local scale = self:GetEffectiveScale();
    if scale ~= self.scale then
        self.scale = scale;
    else
        return
    end
    local a = (self.weight or 1) * PIXEL / scale;
    self.Exclusion:ClearAllPoints();
    self.Exclusion:SetPoint("TOPLEFT", self, "TOPLEFT", a, -a);
    self.Exclusion:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -a, a);
end



--------------------------------------------------------------------------------------------------
NarciChainAnimationMixin = {};

function NarciChainAnimationMixin:Initialize(size, isLinked)
    local offset = 4;
    local unchainedOffset = 8;
    local side = 24;
    local tex = "Interface\\AddOns\\Narcissus\\Art\\Widgets\\LightSetup\\Chain";

    self:SetScale(size / side);
    self.isLinked = isLinked;

    self.chains = {
        self.UpTop, self.DownTop, self.UpBottom, self.DownBottom,
    };
    self.unchains = {
        self.ChainShardUp, self.ChainShardDown, self.UpBroken, self.DownBroken
    };

    for i = 1, #self.chains do
        self.chains[i]:SetTexture(tex, nil, nil, "TRILINEAR");
        self.chains[i]:SetSize(side, side);
        if i % 2 == 1 then
            self.chains[i]:SetPoint("CENTER", self, "CENTER", offset, offset);
        else
            self.chains[i]:SetPoint("CENTER", self, "CENTER", -offset, -offset);
        end
    end

    self.UpBroken:SetSize(side, side);
    self.UpBroken:SetTexture(tex, nil, nil, "TRILINEAR");
    self.UpBroken:SetPoint("CENTER", self, "CENTER", unchainedOffset, unchainedOffset);
    self.DownBroken:SetSize(side, side);
    self.DownBroken:SetTexture(tex, nil, nil, "TRILINEAR");
    self.DownBroken:SetPoint("CENTER", self, "CENTER", -unchainedOffset, -unchainedOffset);

    local tex2 = "Interface\\AddOns\\Narcissus\\Art\\Widgets\\LightSetup\\ChainShard";
    self.ChainShardUp:SetSize(side/2, side/2);
    self.ChainShardUp:SetTexture(tex2, nil, nil, "TRILINEAR");
    self.ChainShardDown:SetSize(side/2, side/2);
    self.ChainShardDown:SetTexture(tex2, nil, nil, "TRILINEAR");

    local tex3 = "Interface\\AddOns\\Narcissus\\Art\\Widgets\\LightSetup\\ChainWave";
    self.WaveExpand:SetSize(side/2, side/2);
    self.WaveExpand:SetTexture(tex3, nil, nil, "TRILINEAR");

    for i = 1, #self.chains do
        self.chains[i]:SetShown(isLinked);
    end
    for i = 1, #self.unchains do
        self.unchains[i]:SetShown(not isLinked);
    end
end

function NarciChainAnimationMixin:Switch()
    self:StopAnimating();
    local isLinked = not self.isLinked;
    self.isLinked = isLinked;
    if isLinked then
        for i = 1, #self.unchains do
            self.unchains[i]:Hide();
        end
        for i = 1, #self.chains do
            self.chains[i]:Show();
            self.chains[i].Link:Play();
        end
    else
        for i = 1, #self.unchains do
            self.unchains[i]:Show();
            self.unchains[i].Unlink:Play();
        end
        for i = 1, #self.chains do
            self.chains[i]:Hide();
        end
        self.WaveExpand.Unlink:Play();
    end
    --[[
    if not self.isPlayingSound then
        self.isPlayingSound = true;
        After(0.5, function() self.isPlayingSound = nil end);
        if isLinked then
            PlaySound(1188, "SFX", false);
        else
            PlaySound(112052, "SFX", false);
        end
    end
    --]]
end


---------------------------------------------------------------------
--Create a rectangle with stroke to indicate the area is interactable
NarciInteractableAreaIndicatorMixin = {};

function NarciInteractableAreaIndicatorMixin:OnLoad()
    self.Exclusion:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Masks\\Exclusion", "CLAMPTOWHITE", "CLAMPTOWHITE", "NEAREST");
    self:UpdateStroke();
end

function NarciInteractableAreaIndicatorMixin:SetStrokeSize(pixel)
    self.pixel = pixel;
    self:UpdateStroke();
end

function NarciInteractableAreaIndicatorMixin:SetStrokeColor(r, g, b)
    self.Stroke:SetColorTexture(r, g, b);
end

function NarciInteractableAreaIndicatorMixin:OnShow()
    local scale = self:GetEffectiveScale();
    if scale ~= self.scale then
        self:UpdateStroke();
    end
end

function NarciInteractableAreaIndicatorMixin:UpdateStroke()
    local pixelSize = NarciAPI.GetPixelForWidget(self, self.pixel or 2);
    self.Exclusion:SetPoint("TOPLEFT", self, "TOPLEFT", pixelSize, -pixelSize);
    self.Exclusion:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -pixelSize, pixelSize);
end

function NarciInteractableAreaIndicatorMixin:OnEnter()
    self.Stroke:Show();
end

function NarciInteractableAreaIndicatorMixin:OnLeave()
    self.Stroke:Hide();
end



---------------------------------------------------------------------
--Keybinding
--internalAction: hotkeys used within Narcissus
--externalAction: hotkeys used to open Narcissus panels
local KeyBindingButtonOverlay;

local function ReleaseOverlay()
    if KeyBindingButtonOverlay then
        KeyBindingButtonOverlay:Hide();
        KeyBindingButtonOverlay:StopAnimating();
    end
end

local function AnchorOverlayToBindingButton(button, colorType, descriptionText)
    --colorType 1 red   2 yellow    3 green
    local f = KeyBindingButtonOverlay;
    if not f then
        KeyBindingButtonOverlay = CreateFrame("Frame", "Test", button, "NarciLineBorderTemplate");
        f = KeyBindingButtonOverlay;
        f.Description = f:CreateFontString(nil, "OVERLAY", "NarciNonInteractiveFont");
        f.Description:SetPoint("TOPLEFT", f, "BOTTOMLEFT", 0, -6);
        f:SetScript("OnHide", ReleaseOverlay);

        f.ConfirmButton = CreateFrame("Button", nil, f, "NarciConfirmButtonTemplate");
        f.ConfirmButton:SetPoint("LEFT", f, "RIGHT", 6, 0);

        local ag = f:CreateAnimationGroup();
        f.FadeOut = ag;
        ag:SetScript("OnFinished", function()
            f:Hide();
        end);
        local a1 = ag:CreateAnimation("Alpha");
        a1:SetDuration(0.5);
        a1:SetFromAlpha(1);
        a1:SetToAlpha(0);
        a1:SetOrder(1);
        a1:SetStartDelay(1.5);
    end

    f:Hide();
    f:ClearAllPoints();
    f:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0);
    f:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0);
    f:SetParent(button);
    f.ConfirmButton.parentObject = button;

    f.Description:SetText(descriptionText);
    if descriptionText then
        f.Description:Show();
    else
        f.Description:Hide();
    end
    if colorType == 1 then
        f:SetColor(0.9333, 0.1961, 0.1412);
        f.Description:SetTextColor(0.9333, 0.1961, 0.1412);
        f.FadeOut:Play();
        f.ConfirmButton:Hide();
    elseif colorType == 2 then
        f:SetColor(0.9882, 0.9294, 0);
        f.Description:SetTextColor(0.9882, 0.9294, 0);
        f.ConfirmButton:Show();
    elseif colorType == 3 then
        f:SetColor(0.4862, 0.7725, 0.4627)
        f.Description:SetTextColor(0.4862, 0.7725, 0.4627);
        f.FadeOut:Play();
        f.ConfirmButton:Hide();
    end

    f:SetAlpha(1);
    f:Show();
end

local function GetInternalBindingKey(self)
    if self.actionName then
        return NarcissusDB[self.actionName] or NOT_BOUND
    else
        return "No Action"
    end
end

local function GetExternalBindingKey(self)
    if self.actionName then
        return GetBindingKey(self.actionName) or NOT_BOUND
    else
        return "No Action"
    end
end

local function ClearBindingKey(actionName)
    local key1, key2 = GetBindingKey(actionName);
    if key1 then
        SetBinding(key1, nil, 1);
    end
    if key2 then
        SetBinding(key2, nil, 1);
    end
    SaveBindings(1);
end


NarciGenericKeyBindingButtonMixin = {};

local function IsExitKey(key)
    return key == "ESCAPE" or key == "SPACE" or key == "ENTER" or key == "BACKSPACE"
end

local function InternalAction_OnKeydown(self, key)
    if IsExitKey(key) then
        self:ExitKeyBinding();
        return
    end

    if self.actionName then
        self:ExitKeyBinding(true);
        NarcissusDB[self.actionName] = key;
    end
end

local function ExternalAction_OnKeydown(self, key)
    if IsExitKey(key) then
        self:ExitKeyBinding();
        return
    end

    local KeyText = CreateKeyChordStringUsingMetaKeyState(key);

    self.ButtonText:SetText(KeyText);
    self.key = KeyText;
    if not IsKeyPressIgnoredForBinding(key) then
        self:VerifyKey();
    end
end

local function ExternalAction_OnKeyUp(self, key)
    self:VerifyKey();
end

local function KeyBingdingButton_OnEvent(self, event)
    if not (self:IsMouseOver() or ( KeyBindingButtonOverlay and KeyBindingButtonOverlay.ConfirmButton:IsMouseOver() )) then
        self:ExitKeyBinding();
    end
end


function NarciGenericKeyBindingButtonMixin:OnEnter()
    self.Border:SetColor(0.8, 0.8, 0.8);
end

function NarciGenericKeyBindingButtonMixin:OnLeave()
    if not self.isOn then
        self.Border:SetColor(0.25, 0.25, 0.25);
    end
end

function NarciGenericKeyBindingButtonMixin:OnLoad()
    if self.internalActionName then
        self:SetBindingActionInternal(self.internalActionName);
        self.internalActionName = nil;
    elseif self.externalActionName then
        self:SetBindingActionExternal(self.externalActionName);
        self.externalActionName = nil;
    end
end

function NarciGenericKeyBindingButtonMixin:SetBindingActionInternal(actionName)
    self.actionName = actionName;
    self.onKeyDownFunc = InternalAction_OnKeydown;
    self.onKeyUpFunc = EmptyFunc;
    self.getBindingFunc = GetInternalBindingKey;
end

function NarciGenericKeyBindingButtonMixin:SetBindingActionExternal(actionName)
    self.actionName = actionName;
    self.onKeyDownFunc = ExternalAction_OnKeydown;
    self.onKeyUpFunc = ExternalAction_OnKeyUp;
    self.getBindingFunc = GetExternalBindingKey;
end

function NarciGenericKeyBindingButtonMixin:ResetVisualAndScript()
    self.isOn = nil;
    if not self:IsVisible() or not self:IsMouseOver() then
        self.Border:SetColor(0.25, 0.25, 0.25);
    end
    self.ButtonText:SetTextColor(1, 1, 1);
    self.ButtonText:SetShadowColor(0, 0, 0);
    self.ButtonText:SetShadowOffset(0.6, -0.6);
    self.Background:SetColorTexture(0, 0, 0);
    self:SetPropagateKeyboardInput(true)
    self:SetScript("OnKeyDown", nil);
    self:SetScript("OnUpdate", nil);
    self:UnregisterEvent("GLOBAL_MOUSE_DOWN");
end

function NarciGenericKeyBindingButtonMixin:ExitKeyBinding(success)
    self.key = nil;
    After(0, function()
        self:ResetVisualAndScript();
        self:GetBindingKey();
    end)
    if success then
        AnchorOverlayToBindingButton(self, 3, KEY_BOUND);
    else
        ReleaseOverlay();
    end
end

function NarciGenericKeyBindingButtonMixin:VerifyKey(override)
    local key = self.key;
    if not key then
        return;
    end
    if key == "SHIFT" or key=="ALT" or key=="CTRL" then
        self.key = nil;
        self:ExitKeyBinding();
        AnchorOverlayToBindingButton(self, 1, NARCI_INVALID_KEY);
        return false
    else
        local action = GetBindingAction(key);
        if (action and action ~= "" and action ~= self.actionName) and not override then
            AnchorOverlayToBindingButton(self, 2, NARCI_OVERRIDE.." "..GetBindingName(action).." ?");
            return true
        else
            ClearBindingKey(self.actionName);
            if SetBinding(key, self.actionName, 1) then
                SaveBindings(1);    --account wide
                self:ExitKeyBinding(true);
            else
                self:ExitKeyBinding();
                AnchorOverlayToBindingButton(self, 1, ERROR_CAPS);
            end
            return false;
        end
    end
end

function NarciGenericKeyBindingButtonMixin:GetBindingKey()
    self.ButtonText:SetText( self.getBindingFunc(self) );
end

function NarciGenericKeyBindingButtonMixin:UnbindKey()
    self:ResetVisualAndScript();
    if self.defaultKey then
        self.ButtonText:SetText(self.defaultKey or NOT_BOUND);
        NarcissusDB[self.actionName] = self.defaultKey;
    elseif self.actionName then
        ClearBindingKey(self.actionName);

    end
end

function NarciGenericKeyBindingButtonMixin:OnClick(button)
    ReleaseOverlay();
    self.isOn = not self.isOn;

    if button == "LeftButton" then
        if self.isOn then
            self.ButtonText:SetTextColor(0, 0, 0);
            self.ButtonText:SetShadowColor(1, 1, 1);
            self.ButtonText:SetShadowOffset(0.6, -0.6);
            self.Background:SetColorTexture(0.8, 0.8, 0.8);
            self:SetScript("OnKeyDown", self.onKeyDownFunc);
            self:SetScript("OnKeyUp", self.onKeyUpFunc);
            self:SetScript("OnEvent", KeyBingdingButton_OnEvent);
            self:RegisterEvent("GLOBAL_MOUSE_DOWN");
            self:SetPropagateKeyboardInput(false);
        else
            self:ExitKeyBinding();
        end
    else
        self:UnbindKey();
        self:ExitKeyBinding();
    end
end

function NarciGenericKeyBindingButtonMixin:OnHide()
    if self.isOn then
        self:ResetVisualAndScript();
    end
end

function NarciGenericKeyBindingButtonMixin:OnShow()
    self:GetBindingKey();
end




TEMPS = nil;