mirror of
https://github.com/miguelmota/cointop
synced 2024-11-10 13:10:26 +00:00
Merge branch 'lyricnz-feature/portfolio-buy2'
This commit is contained in:
commit
b921c091d6
@ -69,6 +69,9 @@ func ActionsMap() map[string]bool {
|
|||||||
"move_down_or_next_page": true,
|
"move_down_or_next_page": true,
|
||||||
"show_price_alert_add_menu": true,
|
"show_price_alert_add_menu": true,
|
||||||
"sort_column_balance": true,
|
"sort_column_balance": true,
|
||||||
|
"sort_column_cost": true,
|
||||||
|
"sort_column_pnl": true,
|
||||||
|
"sort_column_pnl_percent": true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,10 @@ type Coin struct {
|
|||||||
// for favorites
|
// for favorites
|
||||||
Favorite bool
|
Favorite bool
|
||||||
// for portfolio
|
// for portfolio
|
||||||
Holdings float64
|
Holdings float64
|
||||||
Balance float64
|
Balance float64
|
||||||
|
BuyPrice float64
|
||||||
|
BuyCurrency string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllCoins returns a slice of all the coins
|
// AllCoins returns a slice of all the coins
|
||||||
|
@ -92,6 +92,7 @@ type State struct {
|
|||||||
tableCompactNotation bool
|
tableCompactNotation bool
|
||||||
favoritesCompactNotation bool
|
favoritesCompactNotation bool
|
||||||
portfolioCompactNotation bool
|
portfolioCompactNotation bool
|
||||||
|
enableMouse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cointop cointop
|
// Cointop cointop
|
||||||
@ -125,8 +126,10 @@ type Cointop struct {
|
|||||||
|
|
||||||
// PortfolioEntry is portfolio entry
|
// PortfolioEntry is portfolio entry
|
||||||
type PortfolioEntry struct {
|
type PortfolioEntry struct {
|
||||||
Coin string
|
Coin string
|
||||||
Holdings float64
|
Holdings float64
|
||||||
|
BuyPrice float64
|
||||||
|
BuyCurrency string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Portfolio is portfolio structure
|
// Portfolio is portfolio structure
|
||||||
@ -187,6 +190,9 @@ var DefaultChartRange = "1Y"
|
|||||||
// DefaultCompactNotation ...
|
// DefaultCompactNotation ...
|
||||||
var DefaultCompactNotation = false
|
var DefaultCompactNotation = false
|
||||||
|
|
||||||
|
// DefaultEnableMouse ...
|
||||||
|
var DefaultEnableMouse = true
|
||||||
|
|
||||||
// DefaultMaxChartWidth ...
|
// DefaultMaxChartWidth ...
|
||||||
var DefaultMaxChartWidth = 175
|
var DefaultMaxChartWidth = 175
|
||||||
|
|
||||||
@ -296,6 +302,7 @@ func NewCointop(config *Config) (*Cointop, error) {
|
|||||||
SoundEnabled: true,
|
SoundEnabled: true,
|
||||||
},
|
},
|
||||||
compactNotation: DefaultCompactNotation,
|
compactNotation: DefaultCompactNotation,
|
||||||
|
enableMouse: DefaultEnableMouse,
|
||||||
tableCompactNotation: DefaultCompactNotation,
|
tableCompactNotation: DefaultCompactNotation,
|
||||||
favoritesCompactNotation: DefaultCompactNotation,
|
favoritesCompactNotation: DefaultCompactNotation,
|
||||||
portfolioCompactNotation: DefaultCompactNotation,
|
portfolioCompactNotation: DefaultCompactNotation,
|
||||||
@ -488,7 +495,7 @@ func (ct *Cointop) Run() error {
|
|||||||
defer ui.Close()
|
defer ui.Close()
|
||||||
|
|
||||||
ui.SetInputEsc(true)
|
ui.SetInputEsc(true)
|
||||||
ui.SetMouse(true)
|
ui.SetMouse(ct.State.enableMouse)
|
||||||
ui.SetHighlight(true)
|
ui.SetHighlight(true)
|
||||||
ui.SetManagerFunc(ct.layout)
|
ui.SetManagerFunc(ct.layout)
|
||||||
if err := ct.SetKeybindings(); err != nil {
|
if err := ct.SetKeybindings(); err != nil {
|
||||||
|
@ -49,6 +49,7 @@ type ConfigFileConfig struct {
|
|||||||
RefreshRate interface{} `toml:"refresh_rate"`
|
RefreshRate interface{} `toml:"refresh_rate"`
|
||||||
CacheDir interface{} `toml:"cache_dir"`
|
CacheDir interface{} `toml:"cache_dir"`
|
||||||
CompactNotation interface{} `toml:"compact_notation"`
|
CompactNotation interface{} `toml:"compact_notation"`
|
||||||
|
EnableMouse interface{} `toml:"enable_mouse"`
|
||||||
Table map[string]interface{} `toml:"table"`
|
Table map[string]interface{} `toml:"table"`
|
||||||
Chart map[string]interface{} `toml:"chart"`
|
Chart map[string]interface{} `toml:"chart"`
|
||||||
}
|
}
|
||||||
@ -72,6 +73,7 @@ func (ct *Cointop) SetupConfig() error {
|
|||||||
ct.loadRefreshRateFromConfig,
|
ct.loadRefreshRateFromConfig,
|
||||||
ct.loadCacheDirFromConfig,
|
ct.loadCacheDirFromConfig,
|
||||||
ct.loadCompactNotationFromConfig,
|
ct.loadCompactNotationFromConfig,
|
||||||
|
ct.loadEnableMouseFromConfig,
|
||||||
ct.loadPriceAlertsFromConfig,
|
ct.loadPriceAlertsFromConfig,
|
||||||
ct.loadPortfolioFromConfig,
|
ct.loadPortfolioFromConfig,
|
||||||
}
|
}
|
||||||
@ -227,9 +229,12 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
if !ok || entry.Coin == "" {
|
if !ok || entry.Coin == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
amount := strconv.FormatFloat(entry.Holdings, 'f', -1, 64)
|
tuple := []string{
|
||||||
coinName := entry.Coin
|
entry.Coin,
|
||||||
tuple := []string{coinName, amount}
|
strconv.FormatFloat(entry.Holdings, 'f', -1, 64),
|
||||||
|
strconv.FormatFloat(entry.BuyPrice, 'f', -1, 64),
|
||||||
|
entry.BuyCurrency,
|
||||||
|
}
|
||||||
holdingsIfc = append(holdingsIfc, tuple)
|
holdingsIfc = append(holdingsIfc, tuple)
|
||||||
}
|
}
|
||||||
sort.Slice(holdingsIfc, func(i, j int) bool {
|
sort.Slice(holdingsIfc, func(i, j int) bool {
|
||||||
@ -289,6 +294,7 @@ func (ct *Cointop) ConfigToToml() ([]byte, error) {
|
|||||||
Table: tableMapIfc,
|
Table: tableMapIfc,
|
||||||
Chart: chartMapIfc,
|
Chart: chartMapIfc,
|
||||||
CompactNotation: ct.State.compactNotation,
|
CompactNotation: ct.State.compactNotation,
|
||||||
|
EnableMouse: ct.State.enableMouse,
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
@ -506,6 +512,16 @@ func (ct *Cointop) loadCompactNotationFromConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadCompactNotationFromConfig loads compact-notation setting from config file to struct
|
||||||
|
func (ct *Cointop) loadEnableMouseFromConfig() error {
|
||||||
|
log.Debug("loadEnableMouseFromConfig()")
|
||||||
|
if enableMouse, ok := ct.config.EnableMouse.(bool); ok {
|
||||||
|
ct.State.enableMouse = enableMouse
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadAPIChoiceFromConfig loads API choices from config file to struct
|
// LoadAPIChoiceFromConfig loads API choices from config file to struct
|
||||||
func (ct *Cointop) loadAPIChoiceFromConfig() error {
|
func (ct *Cointop) loadAPIChoiceFromConfig() error {
|
||||||
log.Debug("loadAPIKeysFromConfig()")
|
log.Debug("loadAPIKeysFromConfig()")
|
||||||
@ -584,33 +600,7 @@ func (ct *Cointop) loadPortfolioFromConfig() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if key == "holdings" {
|
} else if key == "holdings" {
|
||||||
holdingsIfc, ok := valueIfc.([]interface{})
|
// Defer until the end to work around premature-save issue
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, itemIfc := range holdingsIfc {
|
|
||||||
tupleIfc, ok := itemIfc.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(tupleIfc) > 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, ok := tupleIfc[0].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
holdings, err := ct.InterfaceToFloat64(tupleIfc[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ct.SetPortfolioEntry(name, holdings); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if key == "compact_notation" {
|
} else if key == "compact_notation" {
|
||||||
ct.State.portfolioCompactNotation = valueIfc.(bool)
|
ct.State.portfolioCompactNotation = valueIfc.(bool)
|
||||||
} else {
|
} else {
|
||||||
@ -620,12 +610,62 @@ func (ct *Cointop) loadPortfolioFromConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ct.SetPortfolioEntry(key, holdings); err != nil {
|
if err := ct.SetPortfolioEntry(key, holdings, 0.0, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process holdings last because it causes a ct.Save()
|
||||||
|
if valueIfc, ok := ct.config.Portfolio["holdings"]; ok {
|
||||||
|
if holdingsIfc, ok := valueIfc.([]interface{}); ok {
|
||||||
|
ct.loadPortfolioHoldingsFromConfig(holdingsIfc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *Cointop) loadPortfolioHoldingsFromConfig(holdingsIfc []interface{}) error {
|
||||||
|
for _, itemIfc := range holdingsIfc {
|
||||||
|
tupleIfc, ok := itemIfc.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(tupleIfc) > 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, ok := tupleIfc[0].(string)
|
||||||
|
if !ok {
|
||||||
|
continue // was not a string
|
||||||
|
}
|
||||||
|
|
||||||
|
holdings, err := ct.InterfaceToFloat64(tupleIfc[1])
|
||||||
|
if err != nil {
|
||||||
|
return err // was not a float64
|
||||||
|
}
|
||||||
|
|
||||||
|
buyPrice := 0.0
|
||||||
|
if len(tupleIfc) >= 3 {
|
||||||
|
if buyPrice, err = ct.InterfaceToFloat64(tupleIfc[2]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buyCurrency := ""
|
||||||
|
if len(tupleIfc) >= 4 {
|
||||||
|
if parseCurrency, ok := tupleIfc[3].(string); !ok {
|
||||||
|
return err // was not a string
|
||||||
|
} else {
|
||||||
|
buyCurrency = parseCurrency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch out - this calls ct.Save() which may save a half-loaded configuration
|
||||||
|
if err := ct.SetPortfolioEntry(name, holdings, buyPrice, buyCurrency); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FiatCurrencyNames is a mpa of currency symbols to names.
|
// FiatCurrencyNames is a map of currency symbols to names.
|
||||||
// Keep these in alphabetical order.
|
// Keep these in alphabetical order.
|
||||||
var FiatCurrencyNames = map[string]string{
|
var FiatCurrencyNames = map[string]string{
|
||||||
"AUD": "Australian Dollar",
|
"AUD": "Australian Dollar",
|
||||||
@ -301,3 +301,20 @@ func CurrencySymbol(currency string) string {
|
|||||||
|
|
||||||
return "?"
|
return "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert converts an amount to another currency type
|
||||||
|
func (ct *Cointop) Convert(convertFrom, convertTo string, amount float64) (float64, error) {
|
||||||
|
convertFrom = strings.ToLower(convertFrom)
|
||||||
|
convertTo = strings.ToLower(convertTo)
|
||||||
|
|
||||||
|
if convertFrom == convertTo {
|
||||||
|
return amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rate, err := ct.api.GetExchangeRate(convertFrom, convertTo, true)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate * amount, nil
|
||||||
|
}
|
||||||
|
@ -85,5 +85,8 @@ func DefaultShortcuts() map[string]string {
|
|||||||
"<": "scroll_left",
|
"<": "scroll_left",
|
||||||
"+": "show_price_alert_add_menu",
|
"+": "show_price_alert_add_menu",
|
||||||
"\\\\": "toggle_table_fullscreen",
|
"\\\\": "toggle_table_fullscreen",
|
||||||
|
"!": "sort_column_cost",
|
||||||
|
"@": "sort_column_pnl",
|
||||||
|
"#": "sort_column_pnl_percent",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,6 +325,12 @@ func (ct *Cointop) SetKeybindingAction(shortcutKey string, action string) error
|
|||||||
fn = ct.Keyfn(ct.CursorDownOrNextPage)
|
fn = ct.Keyfn(ct.CursorDownOrNextPage)
|
||||||
case "move_up_or_previous_page":
|
case "move_up_or_previous_page":
|
||||||
fn = ct.Keyfn(ct.CursorUpOrPreviousPage)
|
fn = ct.Keyfn(ct.CursorUpOrPreviousPage)
|
||||||
|
case "sort_column_cost":
|
||||||
|
fn = ct.Sortfn("cost", true)
|
||||||
|
case "sort_column_pnl":
|
||||||
|
fn = ct.Sortfn("pnl", true)
|
||||||
|
case "sort_column_pnl_percent":
|
||||||
|
fn = ct.Sortfn("pnl_percent", true)
|
||||||
default:
|
default:
|
||||||
fn = ct.Keyfn(ct.Noop)
|
fn = ct.Keyfn(ct.Noop)
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ var SupportedPortfolioTableHeaders = []string{
|
|||||||
"1y_change",
|
"1y_change",
|
||||||
"percent_holdings",
|
"percent_holdings",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
|
"cost_price",
|
||||||
|
"cost",
|
||||||
|
"pnl",
|
||||||
|
"pnl_percent",
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultPortfolioTableHeaders are the default portfolio table header columns
|
// DefaultPortfolioTableHeaders are the default portfolio table header columns
|
||||||
@ -49,12 +53,23 @@ var DefaultPortfolioTableHeaders = []string{
|
|||||||
"24h_change",
|
"24h_change",
|
||||||
"7d_change",
|
"7d_change",
|
||||||
"percent_holdings",
|
"percent_holdings",
|
||||||
|
"cost_price",
|
||||||
|
"cost",
|
||||||
|
"pnl",
|
||||||
|
"pnl_percent",
|
||||||
"last_updated",
|
"last_updated",
|
||||||
}
|
}
|
||||||
|
|
||||||
// HiddenBalanceChars are the characters to show when hidding balances
|
// HiddenBalanceChars are the characters to show when hidding balances
|
||||||
var HiddenBalanceChars = "********"
|
var HiddenBalanceChars = "********"
|
||||||
|
|
||||||
|
var costColumns = map[string]bool{
|
||||||
|
"cost_price": true,
|
||||||
|
"cost": true,
|
||||||
|
"pnl": true,
|
||||||
|
"pnl_percent": true,
|
||||||
|
}
|
||||||
|
|
||||||
// ValidPortfolioTableHeader returns the portfolio table headers
|
// ValidPortfolioTableHeader returns the portfolio table headers
|
||||||
func (ct *Cointop) ValidPortfolioTableHeader(name string) bool {
|
func (ct *Cointop) ValidPortfolioTableHeader(name string) bool {
|
||||||
for _, v := range SupportedPortfolioTableHeaders {
|
for _, v := range SupportedPortfolioTableHeaders {
|
||||||
@ -80,6 +95,25 @@ func (ct *Cointop) GetPortfolioTable() *table.Table {
|
|||||||
headers := ct.GetPortfolioTableHeaders()
|
headers := ct.GetPortfolioTableHeaders()
|
||||||
ct.ClearSyncMap(&ct.State.tableColumnWidths)
|
ct.ClearSyncMap(&ct.State.tableColumnWidths)
|
||||||
ct.ClearSyncMap(&ct.State.tableColumnAlignLeft)
|
ct.ClearSyncMap(&ct.State.tableColumnAlignLeft)
|
||||||
|
|
||||||
|
displayCostColumns := false
|
||||||
|
for _, coin := range ct.State.coins {
|
||||||
|
if coin.BuyPrice > 0 && coin.BuyCurrency != "" {
|
||||||
|
displayCostColumns = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !displayCostColumns {
|
||||||
|
filtered := make([]string, 0)
|
||||||
|
for _, header := range headers {
|
||||||
|
if _, ok := costColumns[header]; !ok {
|
||||||
|
filtered = append(filtered, header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers = filtered
|
||||||
|
}
|
||||||
|
|
||||||
for _, coin := range ct.State.coins {
|
for _, coin := range ct.State.coins {
|
||||||
leftMargin := 1
|
leftMargin := 1
|
||||||
rightMargin := 1
|
rightMargin := 1
|
||||||
@ -301,6 +335,117 @@ func (ct *Cointop) GetPortfolioTable() *table.Table {
|
|||||||
Color: ct.colorscheme.TableRow,
|
Color: ct.colorscheme.TableRow,
|
||||||
Text: lastUpdated,
|
Text: lastUpdated,
|
||||||
})
|
})
|
||||||
|
case "cost_price":
|
||||||
|
text := fmt.Sprintf("%s %s", coin.BuyCurrency, ct.FormatPrice(coin.BuyPrice))
|
||||||
|
if coin.BuyPrice == 0.0 || coin.BuyCurrency == "" {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
if ct.State.hidePortfolioBalances {
|
||||||
|
text = HiddenBalanceChars
|
||||||
|
}
|
||||||
|
symbolPadding := 1
|
||||||
|
ct.SetTableColumnWidth(header, utf8.RuneCountInString(text)+symbolPadding)
|
||||||
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
|
rowCells = append(rowCells,
|
||||||
|
&table.RowCell{
|
||||||
|
LeftMargin: leftMargin,
|
||||||
|
RightMargin: rightMargin,
|
||||||
|
LeftAlign: false,
|
||||||
|
Color: ct.colorscheme.TableRow,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
case "cost":
|
||||||
|
cost := 0.0
|
||||||
|
if coin.BuyPrice > 0 && coin.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(coin.BuyCurrency, ct.State.currencyConversion, coin.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
cost = costPrice * coin.Holdings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text := humanize.FixedMonetaryf(cost, 2)
|
||||||
|
if coin.BuyPrice == 0.0 {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
if ct.State.hidePortfolioBalances {
|
||||||
|
text = HiddenBalanceChars
|
||||||
|
}
|
||||||
|
|
||||||
|
symbolPadding := 1
|
||||||
|
ct.SetTableColumnWidth(header, utf8.RuneCountInString(text)+symbolPadding)
|
||||||
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
|
rowCells = append(rowCells,
|
||||||
|
&table.RowCell{
|
||||||
|
LeftMargin: leftMargin,
|
||||||
|
RightMargin: rightMargin,
|
||||||
|
LeftAlign: false,
|
||||||
|
Color: ct.colorscheme.TableColumnPrice,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
case "pnl":
|
||||||
|
text := ""
|
||||||
|
colorProfit := ct.colorscheme.TableColumnChange
|
||||||
|
if coin.BuyPrice > 0 && coin.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(coin.BuyCurrency, ct.State.currencyConversion, coin.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
profit := (coin.Price - costPrice) * coin.Holdings
|
||||||
|
text = humanize.FixedMonetaryf(profit, 2)
|
||||||
|
if profit > 0 {
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChangeUp
|
||||||
|
} else if profit < 0 {
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChangeDown
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = "?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ct.State.hidePortfolioBalances {
|
||||||
|
text = HiddenBalanceChars
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChange
|
||||||
|
}
|
||||||
|
|
||||||
|
symbolPadding := 1
|
||||||
|
ct.SetTableColumnWidth(header, utf8.RuneCountInString(text)+symbolPadding)
|
||||||
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
|
rowCells = append(rowCells,
|
||||||
|
&table.RowCell{
|
||||||
|
LeftMargin: leftMargin,
|
||||||
|
RightMargin: rightMargin,
|
||||||
|
LeftAlign: false,
|
||||||
|
Color: colorProfit,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
|
case "pnl_percent":
|
||||||
|
profitPercent := 0.0
|
||||||
|
if coin.BuyPrice > 0 && coin.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(coin.BuyCurrency, ct.State.currencyConversion, coin.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
profitPercent = 100 * (coin.Price/costPrice - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colorProfit := ct.colorscheme.TableColumnChange
|
||||||
|
if profitPercent > 0 {
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChangeUp
|
||||||
|
} else if profitPercent < 0 {
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChangeDown
|
||||||
|
}
|
||||||
|
text := fmt.Sprintf("%.2f%%", profitPercent)
|
||||||
|
if coin.BuyPrice == 0.0 {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
if ct.State.hidePortfolioBalances {
|
||||||
|
text = HiddenBalanceChars
|
||||||
|
colorProfit = ct.colorscheme.TableColumnChange
|
||||||
|
}
|
||||||
|
ct.SetTableColumnWidthFromString(header, text)
|
||||||
|
ct.SetTableColumnAlignLeft(header, false)
|
||||||
|
rowCells = append(rowCells,
|
||||||
|
&table.RowCell{
|
||||||
|
LeftMargin: leftMargin,
|
||||||
|
RightMargin: rightMargin,
|
||||||
|
LeftAlign: false,
|
||||||
|
Color: colorProfit,
|
||||||
|
Text: text,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,8 +601,12 @@ func (ct *Cointop) SetPortfolioHoldings() error {
|
|||||||
}
|
}
|
||||||
shouldDelete := holdings == 0
|
shouldDelete := holdings == 0
|
||||||
|
|
||||||
|
// TODO: add fields to form, parse here
|
||||||
|
buyPrice := 0.0
|
||||||
|
buyCurrency := ""
|
||||||
|
|
||||||
idx := ct.GetPortfolioCoinIndex(coin)
|
idx := ct.GetPortfolioCoinIndex(coin)
|
||||||
if err := ct.SetPortfolioEntry(coin.Name, holdings); err != nil {
|
if err := ct.SetPortfolioEntry(coin.Name, holdings, buyPrice, buyCurrency); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,7 +652,7 @@ func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPortfolioEntry sets a portfolio entry
|
// SetPortfolioEntry sets a portfolio entry
|
||||||
func (ct *Cointop) SetPortfolioEntry(coin string, holdings float64) error {
|
func (ct *Cointop) SetPortfolioEntry(coin string, holdings float64, buyPrice float64, buyCurrency string) error {
|
||||||
log.Debug("SetPortfolioEntry()")
|
log.Debug("SetPortfolioEntry()")
|
||||||
ic, _ := ct.State.allCoinsSlugMap.Load(strings.ToLower(coin))
|
ic, _ := ct.State.allCoinsSlugMap.Load(strings.ToLower(coin))
|
||||||
c, _ := ic.(*Coin)
|
c, _ := ic.(*Coin)
|
||||||
@ -511,8 +660,10 @@ func (ct *Cointop) SetPortfolioEntry(coin string, holdings float64) error {
|
|||||||
if isNew {
|
if isNew {
|
||||||
key := strings.ToLower(coin)
|
key := strings.ToLower(coin)
|
||||||
ct.State.portfolio.Entries[key] = &PortfolioEntry{
|
ct.State.portfolio.Entries[key] = &PortfolioEntry{
|
||||||
Coin: coin,
|
Coin: coin,
|
||||||
Holdings: holdings,
|
Holdings: holdings,
|
||||||
|
BuyPrice: buyPrice,
|
||||||
|
BuyCurrency: buyCurrency,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.Holdings = holdings
|
p.Holdings = holdings
|
||||||
@ -564,6 +715,8 @@ func (ct *Cointop) GetPortfolioSlice() []*Coin {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
coin.Holdings = p.Holdings
|
coin.Holdings = p.Holdings
|
||||||
|
coin.BuyPrice = p.BuyPrice
|
||||||
|
coin.BuyCurrency = p.BuyCurrency
|
||||||
balance := coin.Price * p.Holdings
|
balance := coin.Price * p.Holdings
|
||||||
balancestr := fmt.Sprintf("%.2f", balance)
|
balancestr := fmt.Sprintf("%.2f", balance)
|
||||||
if ct.State.currencyConversion == "ETH" || ct.State.currencyConversion == "BTC" {
|
if ct.State.currencyConversion == "ETH" || ct.State.currencyConversion == "BTC" {
|
||||||
@ -688,7 +841,7 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
|
|||||||
records := make([][]string, len(holdings))
|
records := make([][]string, len(holdings))
|
||||||
symbol := ct.CurrencySymbol()
|
symbol := ct.CurrencySymbol()
|
||||||
|
|
||||||
headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"}
|
headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings", "cost_price", "cost", "pnl", "pnl_percent"}
|
||||||
if len(filterCols) > 0 {
|
if len(filterCols) > 0 {
|
||||||
for _, col := range filterCols {
|
for _, col := range filterCols {
|
||||||
valid := false
|
valid := false
|
||||||
@ -785,6 +938,70 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error {
|
|||||||
if hideBalances {
|
if hideBalances {
|
||||||
item[i] = HiddenBalanceChars
|
item[i] = HiddenBalanceChars
|
||||||
}
|
}
|
||||||
|
case "cost_price":
|
||||||
|
if entry.BuyPrice > 0 && entry.BuyCurrency != "" {
|
||||||
|
if humanReadable {
|
||||||
|
item[i] = fmt.Sprintf("%s %s", entry.BuyCurrency, ct.FormatPrice(entry.BuyPrice))
|
||||||
|
} else {
|
||||||
|
item[i] = fmt.Sprintf("%s %s", entry.BuyCurrency, strconv.FormatFloat(entry.BuyPrice, 'f', -1, 64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hideBalances {
|
||||||
|
item[i] = HiddenBalanceChars
|
||||||
|
}
|
||||||
|
case "cost":
|
||||||
|
if entry.BuyPrice > 0 && entry.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(entry.BuyCurrency, ct.State.currencyConversion, entry.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
cost := costPrice * entry.Holdings
|
||||||
|
if humanReadable {
|
||||||
|
item[i] = fmt.Sprintf("%s%s", symbol, humanize.FixedMonetaryf(cost, 2))
|
||||||
|
} else {
|
||||||
|
item[i] = strconv.FormatFloat(cost, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item[i] = "?" // error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hideBalances {
|
||||||
|
item[i] = HiddenBalanceChars
|
||||||
|
}
|
||||||
|
case "pnl":
|
||||||
|
if entry.BuyPrice > 0 && entry.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(entry.BuyCurrency, ct.State.currencyConversion, entry.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
profit := (entry.Price - costPrice) * entry.Holdings
|
||||||
|
if humanReadable {
|
||||||
|
// TODO: if <0 "£-3.71" should be "-£3.71"?
|
||||||
|
item[i] = fmt.Sprintf("%s%s", symbol, humanize.FixedMonetaryf(profit, 2))
|
||||||
|
} else {
|
||||||
|
item[i] = strconv.FormatFloat(profit, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item[i] = "?" // error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hideBalances {
|
||||||
|
item[i] = HiddenBalanceChars
|
||||||
|
}
|
||||||
|
case "pnl_percent":
|
||||||
|
if entry.BuyPrice > 0 && entry.BuyCurrency != "" {
|
||||||
|
costPrice, err := ct.Convert(entry.BuyCurrency, ct.State.currencyConversion, entry.BuyPrice)
|
||||||
|
if err == nil {
|
||||||
|
profitPercent := 100 * (entry.Price/costPrice - 1)
|
||||||
|
if humanReadable {
|
||||||
|
item[i] = fmt.Sprintf("%s%%", humanize.Numericf(profitPercent, 2))
|
||||||
|
} else {
|
||||||
|
item[i] = fmt.Sprintf("%.2f", profitPercent)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
item[i] = "?" // error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hideBalances {
|
||||||
|
item[i] = HiddenBalanceChars
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
records[i] = item
|
records[i] = item
|
||||||
|
@ -68,6 +68,14 @@ func (ct *Cointop) Sort(sortBy string, desc bool, list []*Coin, renderHeaders bo
|
|||||||
return a.AvailableSupply < b.AvailableSupply
|
return a.AvailableSupply < b.AvailableSupply
|
||||||
case "last_updated":
|
case "last_updated":
|
||||||
return a.LastUpdated < b.LastUpdated
|
return a.LastUpdated < b.LastUpdated
|
||||||
|
case "cost_price":
|
||||||
|
return a.BuyPrice < b.BuyPrice
|
||||||
|
case "cost":
|
||||||
|
return (a.BuyPrice * a.Holdings) < (b.BuyPrice * b.Holdings) // TODO: convert?
|
||||||
|
case "pnl":
|
||||||
|
return (a.Price - a.BuyPrice) < (b.Price - b.BuyPrice)
|
||||||
|
case "pnl_percent":
|
||||||
|
return (a.Price - a.BuyPrice) < (b.Price - b.BuyPrice)
|
||||||
default:
|
default:
|
||||||
return a.Rank < b.Rank
|
return a.Rank < b.Rank
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,26 @@ var HeaderColumns = map[string]*HeaderColumn{
|
|||||||
Label: "last [u]pdated",
|
Label: "last [u]pdated",
|
||||||
PlainLabel: "last updated",
|
PlainLabel: "last updated",
|
||||||
},
|
},
|
||||||
|
"cost_price": {
|
||||||
|
Slug: "cost_price",
|
||||||
|
Label: "cost price",
|
||||||
|
PlainLabel: "cost price",
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
Slug: "cost",
|
||||||
|
Label: "[!]cost",
|
||||||
|
PlainLabel: "cost",
|
||||||
|
},
|
||||||
|
"pnl": {
|
||||||
|
Slug: "pnl",
|
||||||
|
Label: "[@]PNL",
|
||||||
|
PlainLabel: "PNL",
|
||||||
|
},
|
||||||
|
"pnl_percent": {
|
||||||
|
Slug: "pnl_percent",
|
||||||
|
Label: "[#]PNL%",
|
||||||
|
PlainLabel: "PNL%",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLabel fetch the label to use for the heading (depends on configuration)
|
// GetLabel fetch the label to use for the heading (depends on configuration)
|
||||||
@ -211,7 +231,7 @@ func (ct *Cointop) UpdateTableHeader() error {
|
|||||||
}
|
}
|
||||||
leftAlign := ct.GetTableColumnAlignLeft(col)
|
leftAlign := ct.GetTableColumnAlignLeft(col)
|
||||||
switch col {
|
switch col {
|
||||||
case "price", "balance":
|
case "price", "balance", "pnl", "cost":
|
||||||
label = fmt.Sprintf("%s%s", ct.CurrencySymbol(), label)
|
label = fmt.Sprintf("%s%s", ct.CurrencySymbol(), label)
|
||||||
}
|
}
|
||||||
if leftAlign {
|
if leftAlign {
|
||||||
@ -265,6 +285,9 @@ func (ct *Cointop) SetTableColumnWidth(header string, width int) {
|
|||||||
prev = prevIfc.(int)
|
prev = prevIfc.(int)
|
||||||
} else {
|
} else {
|
||||||
hc := HeaderColumns[header]
|
hc := HeaderColumns[header]
|
||||||
|
if hc == nil {
|
||||||
|
log.Warnf("SetTableColumnWidth(%s) not found", header)
|
||||||
|
}
|
||||||
prev = utf8.RuneCountInString(ct.GetLabel(hc)) + 1
|
prev = utf8.RuneCountInString(ct.GetLabel(hc)) + 1
|
||||||
switch header {
|
switch header {
|
||||||
case "price", "balance":
|
case "price", "balance":
|
||||||
|
@ -184,6 +184,29 @@ draft: false
|
|||||||
|
|
||||||
Your portfolio is autosaved after you edit holdings. You can also press <kbd>ctrl</kbd>+<kbd>s</kbd> to manually save your portfolio holdings to the config file.
|
Your portfolio is autosaved after you edit holdings. You can also press <kbd>ctrl</kbd>+<kbd>s</kbd> to manually save your portfolio holdings to the config file.
|
||||||
|
|
||||||
|
## How do I include buy/cost price in my portfolio?
|
||||||
|
|
||||||
|
Currently there is no UI for this. If you want to include the cost of your coins in the Portfolio screen, you will need to edit your config.toml
|
||||||
|
|
||||||
|
Each coin consists of four values: coin name, coin amount, cost-price, cost-currency.
|
||||||
|
|
||||||
|
For example, the following configuration includes 100 ALGO at USD1.95 each; and 0.1 BTC at AUD50100.83 each.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
holdings = [["Algorand", "100", "1.95", "USD"], ["Bitcoin", "0.1", "50100.83", "AUD"]]
|
||||||
|
```
|
||||||
|
|
||||||
|
With this configuration, four new columns are useful:
|
||||||
|
|
||||||
|
- `cost_price` the price and currency that the coins were purchased at
|
||||||
|
- `cost` the cost (in the current currency) of the coins
|
||||||
|
- `pnl` the PNL of the coins (current value vs original cost)
|
||||||
|
- `pnl_percent` the PNL of the coins as a fraction of the original cost
|
||||||
|
|
||||||
|
With the holdings above, and the currency set to GBP (British Pounds) cointop will look something like this:
|
||||||
|
|
||||||
|
![portfolio profit and loss](https://user-images.githubusercontent.com/122371/138361142-8e1f32b5-ca24-471d-a628-06968f07c65f.png)
|
||||||
|
|
||||||
## How do I hide my portfolio balances (private mode)?
|
## How do I hide my portfolio balances (private mode)?
|
||||||
|
|
||||||
You can run cointop with the `--hide-portfolio-balances` flag to hide portfolio balances or use the keyboard shortcut <kbd>Ctrl</kbd>+<kbd>space</kbd> on the portfolio page to toggle hide/show.
|
You can run cointop with the `--hide-portfolio-balances` flag to hide portfolio balances or use the keyboard shortcut <kbd>Ctrl</kbd>+<kbd>space</kbd> on the portfolio page to toggle hide/show.
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
apitypes "github.com/cointop-sh/cointop/pkg/api/types"
|
apitypes "github.com/cointop-sh/cointop/pkg/api/types"
|
||||||
"github.com/cointop-sh/cointop/pkg/api/util"
|
"github.com/cointop-sh/cointop/pkg/api/util"
|
||||||
gecko "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3"
|
gecko "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3"
|
||||||
|
"github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3/types"
|
||||||
geckoTypes "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3/types"
|
geckoTypes "github.com/cointop-sh/cointop/pkg/api/vendors/coingecko/v3/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ type Service struct {
|
|||||||
maxResultsPerPage uint
|
maxResultsPerPage uint
|
||||||
maxPages uint
|
maxPages uint
|
||||||
cacheMap sync.Map
|
cacheMap sync.Map
|
||||||
|
cachedRates *types.ExchangeRatesItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCoinGecko new service
|
// NewCoinGecko new service
|
||||||
@ -146,6 +148,45 @@ func (s *Service) GetCoinGraphData(convert, symbol, name string, start, end int6
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCachedExchangeRates returns an indefinitely cached set of exchange rates
|
||||||
|
func (s *Service) GetExchangeRates(cached bool) (*types.ExchangeRatesItem, error) {
|
||||||
|
if s.cachedRates == nil || !cached {
|
||||||
|
rates, err := s.client.ExchangeRates()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.cachedRates = rates
|
||||||
|
}
|
||||||
|
return s.cachedRates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExchangeRate gets the current excange rate between two currencies
|
||||||
|
func (s *Service) GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) {
|
||||||
|
convertFrom = strings.ToLower(convertFrom)
|
||||||
|
convertTo = strings.ToLower(convertTo)
|
||||||
|
if convertFrom == convertTo {
|
||||||
|
return 1.0, nil
|
||||||
|
}
|
||||||
|
rates, err := s.GetExchangeRates(cached)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if rates == nil {
|
||||||
|
return 0, fmt.Errorf("expected rates, received nil")
|
||||||
|
}
|
||||||
|
// Combined rate is convertFrom->BTC->convertTo
|
||||||
|
fromRate, found := (*rates)[convertFrom]
|
||||||
|
if !found {
|
||||||
|
return 0, fmt.Errorf("unsupported currency conversion: %s", convertFrom)
|
||||||
|
}
|
||||||
|
toRate, found := (*rates)[convertTo]
|
||||||
|
if !found {
|
||||||
|
return 0, fmt.Errorf("unsupported currency conversion: %s", convertTo)
|
||||||
|
}
|
||||||
|
rate := toRate.Value / fromRate.Value
|
||||||
|
return rate, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetGlobalMarketGraphData gets global market graph data
|
// GetGlobalMarketGraphData gets global market graph data
|
||||||
func (s *Service) GetGlobalMarketGraphData(convert string, start int64, end int64) (apitypes.MarketGraph, error) {
|
func (s *Service) GetGlobalMarketGraphData(convert string, start int64, end int64) (apitypes.MarketGraph, error) {
|
||||||
days := strconv.Itoa(util.CalcDays(start, end))
|
days := strconv.Itoa(util.CalcDays(start, end))
|
||||||
@ -160,25 +201,10 @@ func (s *Service) GetGlobalMarketGraphData(convert string, start int64, end int6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This API does not appear to support vs_currency and only returns USD, so use ExchangeRates to convert
|
// This API does not appear to support vs_currency and only returns USD, so use ExchangeRates to convert
|
||||||
rate := 1.0
|
// TODO: watch out - this is not cached, so we hit the backend every time!
|
||||||
if convertTo != "usd" {
|
rate, err := s.GetExchangeRate("usd", convertTo, true)
|
||||||
rates, err := s.client.ExchangeRates()
|
if err != nil {
|
||||||
if err != nil {
|
return ret, err
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
if rates == nil {
|
|
||||||
return ret, fmt.Errorf("expected rates, received nil")
|
|
||||||
}
|
|
||||||
// Combined rate is USD->BTC->other
|
|
||||||
btcRate, found := (*rates)[convertTo]
|
|
||||||
if !found {
|
|
||||||
return ret, fmt.Errorf("unsupported currency conversion: %s", convertTo)
|
|
||||||
}
|
|
||||||
usdRate, found := (*rates)["usd"]
|
|
||||||
if !found {
|
|
||||||
return ret, fmt.Errorf("unsupported currency conversion: usd")
|
|
||||||
}
|
|
||||||
rate = btcRate.Value / usdRate.Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var marketCapUSD [][]float64
|
var marketCapUSD [][]float64
|
||||||
|
@ -430,3 +430,11 @@ func getChartInterval(start, end int64) string {
|
|||||||
}
|
}
|
||||||
return interval
|
return interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExchangeRate gets the current excange rate between two currencies
|
||||||
|
func (s *Service) GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) {
|
||||||
|
if convertFrom == convertTo {
|
||||||
|
return 1.0, nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unsupported currency conversion: %s => %s", convertFrom, convertTo)
|
||||||
|
}
|
||||||
|
@ -16,4 +16,5 @@ type Interface interface {
|
|||||||
CoinLink(name string) string
|
CoinLink(name string) string
|
||||||
SupportedCurrencies() []string
|
SupportedCurrencies() []string
|
||||||
Price(name string, convert string) (float64, error)
|
Price(name string, convert string) (float64, error)
|
||||||
|
GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) // I don't love this caching
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,11 @@ func Monetaryf(value float64, precision int) string {
|
|||||||
return f(value, precision, "LC_MONETARY", false)
|
return f(value, precision, "LC_MONETARY", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FixedMonetaryf produces a fixed-precision monetary-value string. See Monetaryf.
|
||||||
|
func FixedMonetaryf(value float64, precision int) string {
|
||||||
|
return f(value, precision, "LC_MONETARY", true)
|
||||||
|
}
|
||||||
|
|
||||||
// borrowed from go-locale/util.go
|
// borrowed from go-locale/util.go
|
||||||
func splitLocale(locale string) (string, string) {
|
func splitLocale(locale string) (string, string) {
|
||||||
// Remove the encoding, if present
|
// Remove the encoding, if present
|
||||||
|
@ -38,12 +38,12 @@ func (ui *UI) SetBgColor(bgColor gocui.Attribute) {
|
|||||||
|
|
||||||
// SetInputEsc enables the escape key
|
// SetInputEsc enables the escape key
|
||||||
func (ui *UI) SetInputEsc(enabled bool) {
|
func (ui *UI) SetInputEsc(enabled bool) {
|
||||||
ui.g.InputEsc = true
|
ui.g.InputEsc = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMouse enables the mouse
|
// SetMouse enables the mouse
|
||||||
func (ui *UI) SetMouse(enabled bool) {
|
func (ui *UI) SetMouse(enabled bool) {
|
||||||
ui.g.Mouse = true
|
ui.g.Mouse = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCursor enables the input field cursor
|
// SetCursor enables the input field cursor
|
||||||
@ -53,7 +53,7 @@ func (ui *UI) SetCursor(enabled bool) {
|
|||||||
|
|
||||||
// SetHighlight enables the highlight active state
|
// SetHighlight enables the highlight active state
|
||||||
func (ui *UI) SetHighlight(enabled bool) {
|
func (ui *UI) SetHighlight(enabled bool) {
|
||||||
ui.g.Highlight = true
|
ui.g.Highlight = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetManagerFunc sets the function to call for rendering UI
|
// SetManagerFunc sets the function to call for rendering UI
|
||||||
|
Loading…
Reference in New Issue
Block a user